Sunteți pe pagina 1din 256

Programming Entity Framework:

DbContext
]ulia Lcrman and Rouan Millcr
Beijing Cambridge Farnham Kln Sebastopol Tokyo
Programming Entity Framework: DbContext
Ly ]ulia Leiman anu Rowan Millei
Copyiight 2012 ]ulia Leiman anu Rowan Millei. All iights ieseiveu.
Piinteu in the Uniteu States ol Ameiica.
PuLlisheu Ly O`Reilly Meuia, Inc., 1005 Giavenstein Highway Noith, SeLastopol, CA 95+72.
O`Reilly Looks may Le puichaseu loi euucational, Lusiness, oi sales piomotional use. Online euitions
aie also availaLle loi most titles (http://ny.sajariboo|son|inc.con). Foi moie inloimation, contact oui
coipoiate/institutional sales uepaitment: (S00) 99S-993S oi corporatcorci||y.con.
Editors: Meghan Blanchette anu
Rachel Roumeliotis
Production Editor: Teiesa Elsey
Cover Designer: Kaien Montgomeiy
Interior Designer: Daviu Futato
Illustrators: RoLeit Romano anu ReLecca Demaiest
Revision History for the First Edition:
2012-02-23 Fiist ielease
See http://orci||y.con/cata|og/crrata.csp?isbn=978111931291 loi ielease uetails.
Nutshell HanuLook, the Nutshell HanuLook logo, anu the O`Reilly logo aie iegisteieu tiauemaiks ol
O`Reilly Meuia, Inc. Progranning Entity Irancwor|: DbContcxt, the image ol a gieat Aliican heion,
anu ielateu tiaue uiess aie tiauemaiks ol O`Reilly Meuia, Inc.
Many ol the uesignations useu Ly manulactuieis anu selleis to uistinguish theii piouucts aie claimeu as
tiauemaiks. Vheie those uesignations appeai in this Look, anu O`Reilly Meuia, Inc., was awaie ol a
tiauemaik claim, the uesignations have Leen piinteu in caps oi initial caps.
Vhile eveiy piecaution has Leen taken in the piepaiation ol this Look, the puLlishei anu authois assume
no iesponsiLility loi eiiois oi omissions, oi loi uamages iesulting liom the use ol the inloimation con-
taineu heiein.
ISBN: 97S-1-++9-31296-1
LSI
133000S69S
Table of Contents
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix
1. Introducing the DbContext API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Getting the DLContext API into Youi Pioject 2
Looking at Some Highlights ol the DLContext API 3
Reuucing anu Simplilying Vays to Voik with a Set 5
Retiieving an Entity Using ID with DLSet.Finu 5
Avoiuing Tiolling Aiounu the Guts ol Entity Fiamewoik 6
Voiking with the BieakAway Mouel 6
Getting the Sample Solution 6
Getting DLContext liom an EDMX Mouel S
Ensuiing DLContext Instances Get Disposeu 11
2. Querying with DbContext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Viiting Queiies with LINQ to Entities 13
Queiying All the Data liom a Set 16
Using LINQ loi Soiting, Filteiing, anu Moie 1S
Finuing a Single OLject 21
Queiying Local Data 2+
Using the Loau Methou to Biing Data into Memoiy 26
Running LINQ Queiies Against Local 27
Voiking with the OLseivaLleCollection Retuineu Ly Local 29
Loauing Relateu Data 30
Lazy Loauing 31
Eagei Loauing 33
Explicit Loauing 36
Checking Il a Navigation Piopeity Has Been Loaueu 3S
Queiying Contents ol a Collection Navigation Piopeity 39
Explicit Loauing a SuLset ol the Contents ol a Navigation Piopeity +1
iii
3. Adding, Changing, and Deleting Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Voiking with Single Entities ++
Auuing New Entities ++
Changing Existing Entities +5
Deleting Existing Entities +6
Multiple Changes at Once 51
The Finu oi Auu Pattein 52
Voiking with Relationships 53
Auuing a Relationship Between OLjects 5+
Changing a Relationship Between OLjects 56
Removing a Relationship Between OLjects 57
Voiking with Change Tiacking 59
Using Snapshot Change Tiacking 60
Unueistanuing Vhen Automatic Change Detection Occuis 60
Contiolling Vhen DetectChanges Is Calleu 60
Using DetectChanges to Tiiggei Relationship Fix-up 63
EnaLling anu Voiking with Change Tiacking Pioxies 6+
Ensuiing the New Instances Get Pioxies 67
Cieating Pioxy Instances loi Deiiveu Types 6S
Fetching Entities Vithout Change Tiacking 69
4. Working with Disconnected Entities Including N-Tier Applications . . . . . . . . . . . . . 71
A Simple Opeiation on a Disconnecteu Giaph 72
Exploiing the Challenges ol N-Tiei 7+
Using Existing N-Tiei Fiamewoiks That Suppoit Giaph Mouilication 76
Using Explicit Opeiations on the Seivei Siue 77
Replaying Changes on the Seivei 77
Unueistanuing How DLContext Responus to Setting the State ol a Single
Entity 7S
Maiking a New Entity as Auueu 79
Maiking an Existing Entity as Unchangeu S0
Maiking an Existing Entity as Mouilieu S1
Registeiing an Existing Entity loi Deletion S3
Voiking with Relationships with anu Vithout Foieign Keys S5
Setting the State loi Multiple Entities in an Entity Giaph SS
Getting the Giaph into the Context SS
Setting the State ol Entities in a Giaph 90
Builuing a Geneiic Appioach to Tiack State Locally 92
Cieating a Geneiic Methou That Can Apply State Thiough Any Giaph 96
Concuiiency Implications 9S
Tiacking Inuiviuually Mouilieu Piopeities 99
Recoiuing Mouilieu Piopeity Names 99
Recoiuing Oiiginal Values 102
iv | Table of Contents
Queiying anu Applying Changes 106
5. Change Tracker API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
Change Tiacking Inloimation anu Opeiations loi a Single Entity 111
Voiking with the State Piopeity 112
Voiking with Cuiient, Oiiginal, anu DataLase Values 113
Voiking with DLPiopeityValues loi Complex Types 119
Copying the Values liom DLPiopeityValues into an Entity 122
Changing Values in a DLPiopeityValues 123
Voiking with Inuiviuual Piopeities 12S
Voiking with Scalai Piopeities 12S
Voiking with Complex Piopeities 131
Voiking with Navigation Piopeities 133
Relieshing an Entity liom the DataLase 137
Change Tiacking Inloimation anu Opeiations loi Multiple Entities 139
Using the Change Tiackei API in Application Scenaiios 1+1
Resolving Concuiiency Conllicts 1+1
Logging Duiing Save Changes 1+7
6. Validating with the Validation API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Delining anu Tiiggeiing Valiuation: An Oveiview 15+
Valiuating a Single OLject on Demanu with GetValiuationResult 155
Specilying Piopeity Rules with ValiuationAttiiLute Data Annotations 157
Valiuating Facets Conliguieu with the Fluent API 15S
Valiuating Unmappeu oi Tiansient Piopeities 15S
Valiuating Complex Types 159
Using Data Annotations with an EDMX Mouel 159
Inspecting Valiuation Result Details 160
Inspecting Inuiviuual Valiuation Eiiois 161
Exploiing Moie ValiuationAttiiLutes 163
Using CustomValiuationAttiiLutes 16+
Valiuating Inuiviuual Piopeities on Demanu 166
Specilying Type-Level Valiuation Rules 166
Using IValiuataLleOLject loi Type Valiuation 167
Valiuating Multiple Rules in IValiuataLleOLject 169
Using CustomValiuationAttiiLutes loi Type Valiuation 171
Unueistanuing How EF ComLines Valiuations 173
Valiuating Multiple OLjects 175
Valiuating Vhen Saving Changes 17S
Reviewing OLjectContext. SaveChanges Voikllow 179
Unueistanuing DLContext.SaveChanges Voikllow 179
DisaLling Valiuate Beloie Save 1S2
Table of Contents | v
7. Customizing Validations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
Oveiiiuing ValiuateEntity in the DLContext 1S3
Consiueiing Dilleient Vays to Leveiage ValiuateEntity 1S7
Upuating Data Duiing SaveChanges 192
Oveiiiuing SaveChanges Vhen Valiuation Occuis 193
Compaiing ValiuateEntity to SaveChanges loi Custom Logic 197
Using the IDictionaiy Paiametei ol ValiuateEntity 19S
Contiolling Vhich Entities Aie Valiuateu in ValiuateEntity 200
8. Using DbContext in Advanced Scenarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Moving Between OLjectContext anu DLContext 203
Accessing OLjectContext Featuies liom a DLContext 20+
Auuing DLContext into Existing .NET + Applications 205
Leveiaging SQL Seivei Opeiatois Exposeu in SglFunctions 20S
Queiying Deiiveu Types with DLSet 209
Unueistanuing the Inteilace Piopeity Limitation 210
Consiueiing Automateu Testing with DLContext 210
Testing with DLSet 211
Exploiing a Scenaiio That Unnecessaiily Queiies the DataLase 212
Reuucing DataLase Hits in Testing with IDLSet 21+
Cieating an IDLSet Implementation 21+
ALstiacting BieakAwayContext loi Tests 217
Reviewing the Implementation 221
Supplying Data to a FakeDLSet 221
Accessing the DataLase Diiectly liom DLContext 222
Executing Queiies with DataLase.SglQueiy anu DLSet.SglQueiy 223
Tiacking Results ol SglQueiy 226
Executing Commanus liom the DataLase Class 226
Pioviuing Multiple Taigeteu Contexts in Youi Application 227
Reusing Classes, Conliguiations, anu Valiuation Acioss Multiple
Contexts 227
Ensuiing That All DLContexts Use a Single DataLase 231
Valiuating Relationship Constiaints anu Othei Valiuations with Mul-
tiple Contexts 232
Getting Coue Fiist to Cieate Full BieakAwayContext DataLase 232
9. Whats Coming Next for Entity Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Unueistanuing Entity Fiamewoik`s Veision NumLeis 235
Entity Fiamewoik 5.0 236
Enums 236
Spatial Data 236
Peiloimance Impiovements 236
Multiple Result Sets liom Stoieu Pioceuuies 237
vi | Table of Contents
TaLle Value Functions 237
Table of Contents | vii
Preface
Miciosolt`s piincipal uata access technology, ADO.NET Entity Fiamewoik, has hau
two majoi ieleases as pait ol the .NET Fiamewoik. .NET 3.5 Liought us the liist veision
ol Entity Fiamewoik, which is coveieu in the liist euition ol Progranning Entity
Irancwor| (O`Reilly). In 2010, Miciosolt .NET + was ieleaseu, containing the next
veision ol Entity Fiamewoik, ieleiieu to as Entity Fiamewoik +. The completely ieviseu
seconu euition ol Progranning Entity Irancwor| (O`Reilly) was ueuicateu to teaching
ieaueis how to use this veision ol Entity Fiamewoik in Visual Stuuio 2010.
Vhen .NET + was ieleaseu, the Entity Fiamewoik team was alieauy haiu at woik on
a new auuition, calleu Coue Fiist, to pioviue an alteinative way ol Luiluing the Entity
Data Mouel that is coie to Entity Fiamewoik. Rathei than using a visual uesignei, Coue
Fiist allows you to cieate the mouel liom youi existing classes. At the same time, the
team uevoteu iesouices to making Entity Fiamewoik easiei to use. They locuseu on
the most commonly useu leatuies anu tasks in Entity Fiamewoik anu Luilt a new API
calleu the DLContext API.
This Look is ueuicateu to teaching ieaueis how to use the leatuies ol the DLContext
API. In auuition to the DbContext class, you`ll linu the DbSet class loi peiloiming set
opeiations, impioveu APIs loi change tiacking anu hanuling concuiiency conllicts, anu
a Valiuation API that integiates with valiuation leatuies alieauy piesent in .NET.
In this Look, you will leain how to gueiy anu upuate uata using the new API, whethei
you aie woiking with inuiviuual oLjects oi giaphs ol oLjects anu theii ielateu uata.
You`ll leain how to take auvantage ol the change tiacking leatuies anu Valiuation.
You`ll linu myiiau samples anu uelve into taking auvantage ol auvanceu leatuies pie-
senteu Ly the API.
Audience
This Look is uesigneu loi .NET uevelopeis who have expeiience with Visual Stuuio
anu uataLase management Lasics. Piioi expeiience with Entity Fiamewoik is Lenelicial
Lut not ieguiieu. The coue samples in the Look aie wiitten in C=, with some ol these
ix
samples also expiesseu in Visual Basic. Theie aie a numLei ol online tools you can use
to conveit snippets ol C= into Visual Basic.
Contents of This Book
This Look contains nine chapteis.
Chaptcr 1, |ntroducing thc DbContcxt AP|
This chaptei pioviues a high-level, enu-to-enu oveiview ol the DLContext API.
You`ll leain why the Entity Fiamewoik team ueciueu to cieate the DLContext API
anu how it makes the Entity Fiamewoik easiei to use. You`ll linu example coue,
Lut theie aie no walkthioughs in this liist chaptei.
Chaptcr 2, Qucrying with DbContcxt
In this chaptei you`ll leain aLout ietiieving uata liom the uataLase using Entity
Fiamewoik`s gueiy capaLilities. You`ll leain how to linu an entity Laseu on its key
anu how to loau all entities ol a given type. You`ll leain how to use Language
Integiateu Queiy (LINQ) to soit anu liltei uata. This chaptei also exploies the
vaiious stiategies loi loauing ielateu uata.
Chaptcr 3, Adding, Changing, and Dc|cting Entitics
Once you`ve leaineu how to gueiy loi uata, this chaptei will covei how to make
changes to that uata anu save those changes to the uataLase. You`ll see how to auu
new uata as well as change anu uelete existing uata. You`ll leain how Entity Fiame-
woik keeps tiack ol changes as you make them anu how it saves them using the
SaveChanges methou.
Chaptcr 1, Wor|ing with Disconncctcd Entitics |nc|uding N-Ticr App|ications
In this chaptei, you`ll leain aLout using Entity Fiamewoik to peisist changes that
weie maue to entities while they weie not Leing manageu Ly a context. This chal-
lenge is most common in N-Tiei applications wheie a seivei component is ie-
sponsiLle loi ietiieving uata anu ietuining it to a client application. The client
application then mouilies this uata anu senus it Lack to the seivei to Le saveu.
You`ll leain aLout vaiious appioaches to solving this challenge anu how the Change
Tiackei API can Le useu to implement them.
Chaptcr 5, Changc Trac|cr AP|
The Change Tiackei API is liist intiouuceu in Chaptei + anu this chaptei is ueui-
cateu to exploiing the iemaining lunctionality ol the change tiackei. You`ll leain
how to access the inloimation that Entity Fiamewoik keeps aLout the state ol youi
entity instances. You`ll also leain aLout the opeiations that can Le peiloimeu liom
the Change Tiackei API, incluuing ielieshing an entity liom the uataLase. This
chaptei wiaps up with some examples ol how the Change Tiackei API can Le useu
to solve some common application ieguiiements.
x | Preface
Chaptcr , \a|idating with thc \a|idation AP|
Chaptei 6 intiouuces the new Valiuation API that integiates with the DLContext
anu how it can Le useu to valiuate changes to youi uata Leloie they aie sent to the
uataLase. This chaptei coveis how the Valiuation API makes use ol the existing
valiuation lunctionality incluueu in the .NET Fiamewoik. You`ll leain how vali-
uation is integiateu into the SaveChanges pipeline anu how you can also tiiggei
valiuation manually. You`ll leain how to set up valiuation iules anu how to inspect
valiuation eiiois when youi uata violates these iules.
Chaptcr 7, Custonizing \a|idations
This chaptei exploies some moie auvanceu leatuies ol the Valiuation API, which
was intiouuceu in Chaptei 6. You`ll leain how to customize the logic useu to val-
iuate entities, incluuing customizing the logic that ueteimines which entities neeu
to Le valiuateu. These auvanceu technigues will allow you to wiite valiuation that
inteiacts with the context, which opens up moie valiuation possiLilities, such as
valiuating the unigueness ol a column. This chaptei will also pioviue guiuance
iegaiuing the uangeis ol using the Valiuation API loi tasks othei than valiuation.
Chaptcr 8, Using DbContcxt in Advanccd Sccnarios
Chaptei S is uevoteu to coveiing some auvanceu lunctionality that`s availaLle in
the DLContext API. You`ll leain aLout technigues loi unit testing anu how to wiite
tests that uon`t hit a uataLase. You`ll also see how to Lypass Entity Fiamewoik`s
gueiy pipeline anu inteiact uiiectly with the uataLase when the neeu aiises. Shoulu
youi ieguiiements exceeu what is possiLle liom the DLContext API, you`ll see how
to uiop uown to the unueilying OLjectContext API. The chaptei wiaps up with a
look at cieating smallei Lounueu contexts that allow you to inteiact with a suLset
ol youi complete mouel.
Chaptcr 9, What`s Coning Ncxt jor Entity Irancwor|
This Look was wiitten Laseu on the leatuies ol the DLContext API availaLle in the
Entity Fiamewoik +.3 ielease. At the time ol wiiting, theie aie a numLei ol pieviews
availaLle that uemonstiate some ol the leatuies that the DLContext API will gain
in upcoming ieleases. This chaptei shaies availaLle inloimation aLout these lutuie
ieleases.
Conventions Used in This Book
The lollowing typogiaphical conventions aie useu in this Look:
|ta|ic
Inuicates new teims, URLs, email auuiesses, lilenames, anu lile extensions.
Constant width
Useu loi piogiam listings, as well as within paiagiaphs to ielei to piogiam elements
such as vaiiaLle oi lunction names, uataLases, uata types, enviionment vaiiaLles,
statements, anu keywoius.
Preface | xi
Constant width bold
Shows commanus oi othei text that shoulu Le typeu liteially Ly the usei.
Constant width italic
Shows text that shoulu Le ieplaceu with usei-supplieu values oi Ly values uetei-
mineu Ly context.
This icon signilies a tip, suggestion, oi geneial note.
This icon inuicates a waining oi caution.
Using Code Examples
This Look is heie to help you get youi joL uone. In geneial, you may use the coue in
this Look in youi piogiams anu uocumentation. You uo not neeu to contact us loi
peimission unless you`ie iepiouucing a signilicant poition ol the coue. Foi example,
wiiting a piogiam that uses seveial chunks ol coue liom this Look uoes not ieguiie
peimission. Selling oi uistiiLuting a CD-ROM ol examples liom O`Reilly Looks uoes
ieguiie peimission. Answeiing a guestion Ly citing this Look anu guoting example
coue uoes not ieguiie peimission. Incoipoiating a signilicant amount ol example coue
liom this Look into youi piouuct`s uocumentation uoes ieguiie peimission.
Ve appieciate, Lut uo not ieguiie, attiiLution. An attiiLution usually incluues the title,
authoi, puLlishei, anu ISBN. Foi example: Progranning Entity Irancwor|: DbCon-
tcxt Ly ]ulia Leiman anu Rowan Millei (O`Reilly). Copyiight 2012 ]ulia Leiman anu
Rowan Millei, 97S-1-++9-31296-1.
Il you leel youi use ol coue examples lalls outsiue laii use oi the peimission given aLove,
leel liee to contact us at pcrnissionsorci||y.con.
Safari Books Online
Salaii Books Online (www.sajariboo|son|inc.con) is an on-uemanu uigital
liLiaiy that ueliveis expeit content in Loth Look anu viueo loim liom the
woilu`s leauing authois in technology anu Lusiness. Technology pioles-
sionals, soltwaie uevelopeis, weL uesigneis, anu Lusiness anu cieative
piolessionals use Salaii Books Online as theii piimaiy iesouice loi ie-
seaich, pioLlem solving, leaining, anu ceitilication tiaining.
xii | Preface
Salaii Books Online olleis a iange ol piouuct mixes anu piicing piogiams loi oigani-
zations, goveinment agencies, anu inuiviuuals. SuLsciiLeis have access to thousanus
ol Looks, tiaining viueos, anu piepuLlication manusciipts in one lully seaichaLle ua-
taLase liom puLlisheis like O`Reilly Meuia, Pientice Hall Piolessional, Auuison-Vesley
Piolessional, Miciosolt Piess, Sams, Que, Peachpit Piess, Focal Piess, Cisco Piess, ]ohn
Viley e Sons, Syngiess, Moigan Kaulmann, IBM ReuLooks, Packt, AuoLe Piess, FT
Piess, Apiess, Manning, New Riueis, McGiaw-Hill, ]ones e Baitlett, Couise Tech-
nology, anu uozens moie. Foi moie inloimation aLout Salaii Books Online, please visit
us online.
How to Contact Us
Please auuiess comments anu guestions conceining this Look to the puLlishei:
O`Reilly Meuia, Inc.
1005 Giavenstein Highway Noith
SeLastopol, CA 95+72
S00-99S-993S (in the Uniteu States oi Canaua)
707-S29-0515 (inteinational oi local)
707-S29-010+ (lax)
Ve have a weL page loi this Look, wheie we list eiiata, examples, anu any auuitional
inloimation. You can access this page at:
http://shop.orci||y.con/product/03920022237.do
To comment oi ask technical guestions aLout this Look, senu email to:
boo|qucstionsorci||y.con
Foi moie inloimation aLout oui Looks, couises, conleiences, anu news, see oui weLsite
at http://www.orci||y.con.
Finu us on FaceLook: http://jaccboo|.con/orci||y
Follow us on Twittei: http://twittcr.con/orci||yncdia
Vatch us on YouTuLe: http://www.youtubc.con/orci||yncdia
Acknowledgments
Ve aie giatelul loi the people who spent theii piecious liee time ieauing thiough anu
even tiying the walkthioughs in this Look anu pioviuing leeuLack. Thanks to Rowan`s
teammates, Aithui Vickeis, Pawel Kauluczka, anu Diego Vega, loi theii help in ensui-
ing oui accuiacy thioughout this Look. Rolanu Civet, Mikael Eliasson, anu Daniel
Veitheim also pioviueu invaluaLle leeuLack that helpeu us line-tune oui explanations
anu oui coue.
Thanks to Miciosolt loi making it possiLle loi Rowan to paiticipate in this pioject.
Preface | xiii
Thanks once again to O`Reilly Meuia, especially oui euitois, Meghan Blanchette anu
Rachel Roumeliotis, loi theii suppoit, theii copyeuiting, anu theii extieme patience as
many scheuule conllicts uelayeu oui piomiseu ueaulines.
xiv | Preface
CHAPTER 1
Introducing the DbContext API
Since its liist ielease, the most ciitical element in Entity Fiamewoik has Leen the
ObjectContext. It is this class that allows us to inteiact with a uataLase using a concep-
tual mouel. The context lets us expiess anu execute gueiies, tiack changes to oLjects
anu peisist those changes Lack to the uataLase. The ObjectContext class inteiacts with
othei impoitant Entity Fiamewoik classes such as the ObjectSet, which enaLles set
opeiations on oui entities in memoiy, anu ObjectQuery, which is the Liains Lehinu
executing gueiies. All ol these classes aie ieplete with leatuies anu lunctionalitysome
ol it complex anu much ol it only necessaiy loi special cases. Altei two iteiations ol
Entity Fiamewoik (in .NET 3.5 SP1 anu .NET +) it was cleai that uevelopeis weie most
commonly using a suLset ol the leatuies, anu unloitunately, some ol the tasks we
neeueu to uo most lieguently weie uillicult to uiscovei anu coue.
Recognizing this, the Entity Fiamewoik team set out to make it easiei loi uevelopeis
to access the most lieguently useu patteins loi woiking with oLjects within Entity
Fiamewoik. Theii solution was a new set ol classes that encapsulate this suLset ol
ObjectContext leatuies. These new classes use the ObjectContext Lehinu the scenes, Lut
uevelopeis can woik with them without having to tangle with the ObjectContext unless
they neeu to specilically use some ol the moie auvanceu leatuies. The new set ol classes
was oiiginally ieleaseu as pait ol Entity Fiamewoik +.1 (EF +.1).
The piominent classes in this simplilieu API suilace aie the DbContext, DbSet, anu
DbQuery. This entiie package ol new logic is ieleiieu to as the DLContext API. The new
API contains moie than just the DbContext class, Lut it is the DbContext that oichestiates
all ol the new leatuies.
The DLContext API is availaLle in the EntityIrancwor|.d|| assemLly, which also con-
tains the logic that uiives Entity Fiamewoik Coue Fiist. This assemLly is sepaiate
liom .NET anu is even ueployeu sepaiately as the EntityFiamewoik NuGet package.
A majoi poition ol the Entity Fiamewoik is pait ol the .NET Fiamewoik (piimaiily
Systcn.Data.Entity.d||). The components that aie incluueu in .NET aie consiueieu the
coie components ol Entity Fiamewoik. The DLContext API is completely uepenuent
on these coie components ol Entity Fiamewoik. The Entity Fiamewoik team has
1
inuicateu that they aie woiking to move moie ol these coie components out ol .NET
anu into the EntityIrancwor|.d|| assemLly. This will allow them to uelivei moie lea-
tuies Letween ieleases ol the .NET Fiamewoik.
In TaLle 1-1, you can see a list ol the high-level leatuies anu classes in the DLContext
API, how they ielate to the API suilace liom Entity Fiamewoik + (EF+), theii geneial
puipose, anu theii Lenelits.
Tab|c 1-1. Ovcrvicw oj DbContcxt AP| jcaturcs
DbContext
API feature
Relevant EF4
feature/class General purpose Benefit of DbContext API
DbContext ObjectContext Represent a session with the database. Provide
query, change tracking and save capabilities.
Exposes and simplifies most
commonly used features of Ob-
jectContext.
DbSet ObjectSet Provide set operations for entity types, such as Add,
Attach and Remove. Inherits from DbQuery to expose
query capabilities.
Exposes and simplifies most
commonly used features of Ob-
jectSet.
DbQuery ObjectQuery Provide querying capabilities. The query functionality of
DbQuery is exposed on DbSet, so
you dont have to interact with
DbQuery directly.
Change
Tracker API
ObjectCon-
text.ObjectSta-
teManager
Get access to change tracking information and op-
erations (e.g., original values, current values) man-
aged by the context.
Simpler and more intuitive API
surface.
Validation API n/a Provide automatic validation of data at the data
layer. This API takes advantage of validation features
already existing in .NET 4.
New to DbContext API.
Code First
Model
Building
n/a Reads classes and code-based configurations to build
in-memory model, metadata and relevant database.
New to DbContext API.
Getting the DbContext API into Your Project
The DLContext API is not ieleaseu as pait ol the .NET Fiamewoik. In oiuei to Le moie
llexiLle (anu lieguent) with ieleasing new leatuies to Coue Fiist anu the DLContext
API, the Entity Fiamewoik team uistiiLutes EntityIrancwor|.d|| thiough Miciosolt`s
NuGet uistiiLution leatuie. NuGet allows you to auu ieleiences to youi .NET piojects
Ly pulling the ielevant DLLs uiiectly into youi pioject liom the VeL. A Visual Stuuio
extension calleu the LiLiaiy Package Managei pioviues an easy way to pull the appio-
piiate assemLly liom the VeL into youi piojects. Figuie 1-1 uisplays a scieenshot ol
the LiLiaiy Package Managei Leing useu to uownloau anu auu the EntityFiamewoik
NuGet package into a pioject.
2 | Chapter 1:Introducing the DbContext API
You can leain moie aLout using NuGet anu the LiLiaiy Package Man-
agei at nuget.oig.
At the time ol this Look`s puLlication (eaily 2012), the cuiient veision
ol EntityFiamewoik package is +.3. Chaptei 9 pioviues an oveiview ol
what to expect in lutuie veisions.
Looking at Some Highlights of the DbContext API
DLContext API is mostly taigeteu at simplilying youi inteiaction with Entity Fiame-
woik, Loth Ly ieuucing the numLei ol methous anu piopeities you neeu to waue
thiough anu Ly pioviuing simplei ways to access commonly useu tasks. In pievious
veisions ol Entity Fiamewoik, these tasks weie olten complicateu to uiscovei anu coue.
Ve have a lew lavoiites that act as gieat amLassauois to the new API, which we`ll shaie
with you heie. You`ll leain moie aLout these as you woik youi way thiough the Look.
The samples useu in this chaptei aie loi explanation puiposes only anu
not intenueu loi you to peiloim in Visual Stuuio. Beginning with the
next chaptei, you`ll linu walkthioughs that you can lollow in Visual
Stuuio.
Iigurc 1-1. Gctting EntityIrancwor|.d|| jron thc Library Pac|agc Managcr
Looking at Some Highlights of the DbContext API | 3
Let`s stait Ly looking at how the DLContext API simplilies the context that we ueline
anu woik with. Ve`ll compaie the ObjectContext anu DbContext Laseu context classes
liom the mouel we`ll Le using in this Look, Laseu on BieakAway Geek Auventuie`s
Lusiness applications. Ve`ll expose gueiyaLle sets ol People, Destinations, anu Trips
Laseu on a Person class, a Destination class, anu a Trip class.
Example 1-1 shows a suLset ol the BreakAwayContext class useu in Entity Fiamewoik
+, Laseu on an ObjectContext. It wiaps up some known types into ObjectSets, which
you can gueiy against.
Exanp|c 1-1. Brca|AwayContcxt that inhcrits jron ObjcctContcxt
public class BreakAwayContext : ObjectContext
{
private ObjectSet<Person> _ people;
private ObjectSet<Destination> _destinations;
private ObjectSet<Trip> _trips;
public ObjectSet<Person> People
{
get { return _people ?? (_people = CreateObjectSet<Person>("People")); }
}
public ObjectSet< Destination > Contacts
{
get { return _ destinations?? (_destinations =
CreateObjectSet< Destination >("Destinations")); }
}
public ObjectSet<Trip> Trips
{
get { return _ trips?? (_trips = CreateObjectSet<Trip>("Trips")); }
}
}
Example 1-2 shows the same context anu sets using a DbContext anu DbSets insteau.
Alieauy you can see a Lig impiovement. You can use automatic piopeities with DbSet
(that`s the simplilieu get;set; pattein), something you can`t uo with ObjectSet. This
makes loi much cleanei coue iight out ol the gate. Theie is a CreateDbSet methou that`s
ielative to CreateObjectSet, Lut you aien`t ieguiieu to use it loi the puipose ol cieating
a DbSet when you have no othei logic to apply.
Exanp|c 1-2. Brca|AwayContcxt inhcriting jron DbContcxt
public class BreakAwayContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Destination> Destinations { get; set; }
public DbSet<Trip> Trips { get; set; }
}
4 | Chapter 1:Introducing the DbContext API
Reducing and Simplifying Ways to Work with a Set
In Entity Fiamewoik +, theie aie a numLei ol tasks that you can achieve liom Loth
ObjectContext anu ObjectSet. Foi example, when auuing an oLject instance to a set,
you can use ObjectContext.AddObject oi ObjectSet.AddObject. Vhen auuing an oLject
into the context, the context neeus to know which set it Lelongs to. Vith
ObjectContext.AddObject, you must specily the set using a stiing, loi example:
context.AddObject("Trips", newTrip);
Vhen ObjectSet was intiouuceu in Entity Fiamewoik +, it came with its own
AddObject methou. This path alieauy pioviues knowleuge ol the set so you can simply
pass in the oLject:
context.Trips.AddObject(newTrip);
Vith this new methou availaLle, the only ieason ObjectContext.AddObject continueu
to exist in Entity Fiamewoik + was loi Lackwaiu compatiLility with eailiei veisions.
But uevelopeis who weie not awaie ol this ieason weie conluseu Ly the lact that theie
weie two options.
Because the DLContext API is new, we uon`t have to woiiy aLout Lackwaiu compat-
iLility, so the DbContext uoes not have a uiiect methou loi auuing an oLject. Auuition-
ally, iathei than pioviuing the clunky AddObject methou in DbSet, the methou name is
now simply Add:
context.Trips.Add(newTrip);
ObjectContext also has AttachObject anu DeleteObject. DbContext uoes not have these
methous eithei. DbSet has Attach anu Remove, which aie eguivalent to ObjectSet`s
Attach anu Delete OLject. You`ll leain moie aLout inteiacting with DbSet Leginning in
Chaptei 2.
Retrieving an Entity Using ID with DbSet.Find
One task that uevelopeis peiloim lieguently is ietiieving an entity Ly pioviuing its key
value. Foi example, you may have access to the PersonId value ol a Person in a vaiiaLle
nameu _personId anu woulu like to ietiieve the ielevant peison uata.
Typically you woulu constiuct anu execute a LINQ to Entities gueiy. Heie`s a gueiy
that uses the SingleOrDefault LINQ methou to liltei on PersonId when executing a
gueiy on context.People:
context.People.SingleOrDefault(p => p.PersonId == _personId)
Have you wiitten that coue so olten that you linally wiote a wiappei methou so you
coulu pass the key value in anu it woulu execute the LINQ gueiy loi you? Yeah, us too.
Now DbSet has that shoitcut Luilt in with the Find methou, which will ietuin an entity
whose key piopeity matches the value passeu into the methou:
context.People.Find(_personId)
Looking at Some Highlights of the DbContext API | 5
Find has anothei Lenelit. Vhile the SingleOrDefault gueiy aLove will always gueiy the
uataLase, Find will liist check to see il that paiticulai peison is alieauy in memoiy,
Leing tiackeu Ly the context. Il so, that`s what will Le ietuineu. Il not, it will make the
tiip to the uataLase. Unuei the coveis, DbContext is executing logic on ObjectContext
to peiloim the necessaiy tasks. You`ll leain moie aLout DbSet.Find in Chaptei 2.
Avoiding Trolling Around the Guts of Entity Framework
These aie just a lew examples ol how much moie natuial it is to woik with the DLCon-
text API than the ObjectContext API. Il you ieau Progranning Entity Irancwor|, 2c,
you might Le lamiliai with the many extension methous that ]ulie cieateu anu com-
Lineu to simplily ietiieving instances ol oLjects that aie Leing tiackeu Ly the context
liom the ObjectStateManager. One simple piopeity, DbSet.Local, now peiloims that
same task. In lact, thanks to the new Change Tiackei API, theie`s no neeu to uig into
the ObjectStateManager. It`s not even pait ol the DbContext API. Insteau you can use
DbContext.Entry oi DbContext.Entries to linu anu even change the inloimation Leing
tiackeu Ly the context. You`ll leain moie aLout these methous in Chaptei 5.
Working with the BreakAway Model
This Look lollows the mouel Luilt aiounu the BieakAway Geek Auventuies company
in the Look Progranning Entity Irancwor|: Codc Iirst (O`Reilly). Even though the
examples in this Look use a mouel uelineu with Coue Fiist, the concepts apply just as
well to a mouel Luilt using the uesignei.
Getting the Sample Solution
Il you want to lollow along the Look`s examples, you`ll neeu to uownloau the staiting
solution liom the uownloau page ol the Look`s weLsite at http://|carncntityjrancwor|
.con/down|oads. In the solution you`ll linu thiee piojects:
1. The Modc| pioject contains the uomain classes, which aie conliguieu using Data
Annotations.
2. The DataAcccss pioject contains the BreakAwayContext class that ueiives liom
DbContext.
3. The Brca|AwayConso|c pioject is a console application wheie you can auu anu
execute methous as we exploie the many capaLilities ol the DLContext API.
Vhen using Coue Fiist you Legin with youi classes. Coue Fiist uses convention to inlei
what the schema ol the ielevant uataLase looks like anu how Entity Fiamewoik can
tianslate liom youi classes to that uataLase. Coue Fiist`s conventions uo not always
align with youi ieality, howevei, so you can tweak how Coue Fiist maps youi classes
to the uataLase Ly peiloiming auuitional conliguiation. Theie aie two ways to apply
this auuitional conliguiation. One is Ly auuing attiiLutes to youi classes anu theii
6 | Chapter 1:Introducing the DbContext API
piopeities (calleu Data Annotations) anu the othei is Ly using Coue Fiist`s Fluent API.
In the Coue Fiist Look, we showeu you how to use Loth leatuies anu Luilt up two
veisions ol the BieakAway mouelone that uses Data Annotations to conliguie the
mappings anu the othei using the Fluent API.
The examples in this Look anu the sample uownloau aie Laseu on the veision ol the
mouel that uses Data Annotations. Foi example, the liist class you`ll encountei in
Chaptei 2 is the Destination class, which is uisplayeu heie in Example 1-3.
Exanp|c 1-3. A c|ass using Data Annotations to spccijy Codc Iirst conjiguration
[Table("Locations", Schema = "baga")]
public class Destination
{
public Destination()
{
this.Lodgings = new List<Lodging>();
}
[Column("LocationID")]
public int DestinationId { get; set; }
[Required, Column("LocationName")]
[MaxLength(200)]
public string Name { get; set; }
public string Country { get; set; }
[MaxLength(500)]
public string Description { get; set; }
[Column(TypeName = "image")]
public byte[] Photo { get; set; }
public string TravelWarnings { get; set; }
public string ClimateInfo { get; set; }
public List<Lodging> Lodgings { get; set; }
}
The Destination class has a numLei ol Data Annotations. It Legins with a Table at-
tiiLute inuicating that the Destination class will map to a uataLase taLle nameu Loca
tions which has the schema baga. Vithout this annotation, Coue Fiist woulu piesume
the taLle name is the pluial ol Destination (Destinations), in the uelault dbo schema.
The DestinationId piopeity is conliguieu to map to a column in the taLle nameu
LocationId anu the Name column to one calleu LocationName, with a max length ol 200.
The System.Data.SqlClient pioviuei will uelault to specilying that the LocationName
column is an nvarchar(200). Anothei annotation ensuies that Coue Fiist unueistanus
that the Photo piopeity maps to a column whose type is image.
The BreakAway context class inheiits liom System.Data.Entity.DbContext, the cential
class ol the DLContext API. It contains piopeities that iellect sets ol the vaiious mouel
classes containeu in the solutions. Foi example a piopeity nameu Destinations ietuins
a gueiyaLle set ol Destination types. The gueiyaLle set comes in the loim ol a DbSet
classanothei piece ol the DLContext API. Example 1-+ gives you a sampling ol
Working with the BreakAway Model | 7
piopeities in the BreakAwayContext class, which you`ll see moie ol Leginning with the
next chaptei.
Exanp|c 1-1. A contcxt c|ass cxposing thrcc DbScts that wrap donain c|asscs
public class BreakAwayContext : DbContext
{
public DbSet<Destination> Destinations { get; set; }
public DbSet<Lodging> Lodgings { get; set; }
public DbSet<Trip> Trips { get; set; }
}
Coue Fiist can eithei cieate a uataLase loi you oi Le useu to map to an existing uataLase.
By uelault, Coue Fiist will cieate a uataLase loi you on youi local SQL Expiess instance,
using the namespace-gualilieu context name as the name loi the uataLase. Foi the sake
ol simplicity, the examples in this Look will let Coue Fiist cieate a uataLase automat-
ically. Altei iunning some ol the sample coue, you will linu a DataAccess.BreakAway
Context uataLase on youi local SQL Expiess instance.
You can leain much moie aLout Coue Fiist, its conliguiations, anu its
uataLase inteiactions in Progranning Entity Irancwor|: Codc Iirst.
Getting DbContext from an EDMX Model
Although the Look samples use Coue Fiist, you may not Le using Coue Fiist to uesciiLe
the mouel in youi applications. Il, insteau, you aie using the Entity Data Mouel De-
signei anu want to take auvantage ol the DLContext API, theie`s an easy way to uo
that. Visual Stuuio uses the Text Template Tiansloimation Toolkit (T+) geneiatoi to
geneiate the uelault ObjectContext anu classes liom an EDMX lile. The geneiatoi uses
a uelault template, which is uesigneu to cieate class lielus in a paiticulai way. Vith the
uelault template, each entity in the mouel Lecomes a class that inheiits liom EntityOb
ject anu a sepaiate class is geneiateu to manage the entities that inheiits liom Object
Context.
Miciosolt pioviues alteinative templates that you can use to geneiate POCO classes
anu a DbContext-Laseu context liom the EDMX. These aie availaLle online anu can
easily Le selecteu liom within the Entity Data Mouel Designei:
1. Open youi EDMX lile in the Entity Data Mouel uesignei.
2. Right-click on the mouel Lackgiounu anu select Auu Coue Geneiation Item.
as shown in Figuie 1-2.
3. In the Auu New Item winuow, select Online Templates liom the lelt menu anu
then seaich loi DLContext. Select the DLContext Geneiatoi template liom the
seaich iesults, entei a name, anu click Auu (Figuie 1-3).
8 | Chapter 1:Introducing the DbContext API
As a iesult, two templates will Le auueu to youi pioject. One is a context template
(Modc|.Contcxt.tt in the sample shown in Figuie 1-+), which geneiates a class that
inheiits liom DbContext, shown in Example 1-5.
Iigurc 1-2. Adding a ncw T1 tcnp|atc codc gcncration itcn jron thc nodc|`s contcxt ncnu
Iigurc 1-3. Sc|ccting thc DbContcxt Gcncrator tcnp|atc
Working with the BreakAway Model | 9
Iigurc 1-1. Projcct with .tt tcnp|atc ji|cs and thcir codc-gcncratcd .cs ji|cs
Exanp|c 1-5. Gcncratcd BAEntitics c|ass inhcriting jron DbContcxt
public partial class BAEntities : DbContext
{
public BAEntities()
: base("name=BAEntities")
{
this.Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Activity> Activities { get; set; }
public DbSet<Contact> Contacts { get; set; }
public DbSet<CustomerType> CustomerTypes { get; set; }
public DbSet<Equipment> EquipmentSet { get; set; }
public DbSet<Trip> Trips { get; set; }
public DbSet<Destination> Destinations { get; set; }
public DbSet<Lodging> Lodgings { get; set; }
10 | Chapter 1:Introducing the DbContext API
public DbSet<Payment> Payments { get; set; }
}
The seconu template (also shown in Figuie 1-+), heie calleu Modc|.tt, is the one that
geneiates POCO classes loi each ol the entities in youi EDMX mouel. As you saw aLove,
the context class exposes each ol these POCO types in a DbSet.
Using this template, you can take auvantage ol an existing visual mouel anu still Lenelit
liom the DLContext API, which you`ll Le leaining aLout in this Look.
Ensuring DbContext Instances Get Disposed
A DbContext (anu its unueilying ObjectContext) aie iesponsiLle loi managing anu
tiacking changes to instances ol the classes in its mouel. These classes aie also iespon-
siLle loi managing a connection to the uataLase. It`s impoitant to ensuie that any ie-
souices useu to peiloim these opeiations aie cleaneu up when the DbContext instance
is no longei neeueu. DbContext implements the stanuaiu .NET IDisposable inteilace,
which incluues a Dispose methou that will ielease any such iesouices.
The examples in this Look will make use ol the using pattein, which will take caie ol
uisposing the context when the using Llock completes (Example 1-6). Il youi applica-
tion uoesn`t make use ol the using pattein, ensuie that the Dispose methou is calleu on
any DbContext instances when they aie no longei neeueu.
Exanp|c 1-. |nstantiating and disposing a contcxt with thc using pattcrn
public static List<Destination> GetDestinations()
{
using (var context = new BreakAwayContext())
{
var query= from d in context.Destinations
orderby d.Name
select d;
return query.ToList();
}
}
Ensuring DbContext Instances Get Disposed | 11
CHAPTER 2
Querying with DbContext
Theie aie two things that almost eveiy application that accesses a uataLase has in com-
mon: the neeu to ietiieve uata liom the uataLase anu to save changes to that uata Lack
into the uataLase. Ovei the next two chapteis you will see how the DLContext API
makes it easy to achieve these tasks using the Entity Fiamewoik. The locus ol this
chaptei will Le on ietiieving uata liom the uataLase.
One ol the gieat Lenelits ol using an OLject Relational Mappei (ORM), such as Entity
Fiamewoik, is that once we have set up the mapping, we can inteiact with oui uata in
teims ol the oLjects anu piopeities that make up oui mouel, iathei than taLles anu
columns. Vhen gueiying loi oLjects, this means we no longei neeu to know how to
wiite gueiies using the SQL syntax ol oui uataLase.
Writing Queries with LINQ to Entities
Entity Fiamewoik gueiies aie wiitten using a .NET Fiamewoik leatuie known as Lan-
guage Integiateu Queiy, oi LINQ loi shoit. As the name suggests, LINQ is tightly
integiateu with the .NET piogiamming expeiience anu pioviues a stiongly typeu gueiy
language ovei youi mouel. Strong|y typcd simply means that the gueiy is uelineu using
the classes anu piopeities that make up youi mouel. This pioviues a numLei ol Lenelits
such as compile-time checks to ensuie youi gueiies aie valiu anu the aLility to pioviue
IntelliSense as you wiite youi gueiies.
LINQ is a geneial gueiy liamewoik anu isn`t specilic to Entity Fiamewoik, oi even
uataLases loi that mattei. A LINQ Pioviuei is iesponsiLle loi taking youi LINQ gueiy,
tianslating it into a gueiy against the uata, anu then ietuining iesults. Foi Entity
Fiamewoik this pioviuei is known as LINQ to Entities anu is iesponsiLle loi taking
youi LINQ gueiy anu tianslating it into a SQL gueiy against the uataLase you aie
taigeting. The inloimation you supplieu to Entity Fiamewoik aLout the shape ol youi
mouel anu how it maps to the uataLase is useu to peiloim this tianslation. Once the
gueiy ietuins, Entity Fiamewoik is iesponsiLle loi copying the uata into instances ol
the classes that make up youi mouel.
13
The capaLilities ol LINQ anu its use within Entity Fiamewoik aie Leyonu the scope ol
this Look. This chaptei will pioviue an oveiview to help you get up anu iunning with
gueiies using DbContext, Lut is not an exhaustive gueiy guiue. Progranning Entity
Irancwor|, 2c, pioviues a much moie in-uepth look at the gueiy capaLilities ol Entity
Fiamewoik, not only in Chaptei 3 anu Chaptei +, which aie ueuicateu to gueiying, Lut
thioughout the Look.
In auuition to LINQ, Entity Fiamewoik also suppoits a text-Laseu gueiy
language known as Entity SQL, oi ESQL loi shoit. ESQL is typically
useu in moie auvanceu scenaiios wheie gueiies neeu to Le uynamically
constiucteu at iuntime. Because ESQL is text-Laseu, it is also uselul in
scenaiios wheie the application neeus to Luilu a gueiy against a mouel
that isn`t known until iuntime. Given that ESQL is less commonly useu,
it is not exposeu uiiectly on the DLContext API. Il youi application
ieguiies the use ol ESQL, you will neeu to access the OLjectContext API
using the IOLjectContextAuaptei inteilace.
To lollow along with the examples in this Look you will neeu a Visual Stuuio solution
containing a console application that ieleiences the BAGA mouel Luilt in Progranning
Entity Irancwor|: Codc Iirst. You can uownloau a pieLuilt solution liom http://|car
ncntityjrancwor|.con/down|oads. This pieLuilt solution also incluues a uataLase ini-
tializei that will ieset the uataLase anu inseit some seeu uata into the uataLase each
time you iun the application. The seeu uata is useu in the examples thioughout this
Look.
Code First Migrations
Entity Fiamewoik +.3 incluues a new Coue Fiist Migiations leatuie that allows you to
inciementally evolve the uataLase schema as youi mouel changes ovei time. Foi most
uevelopeis, this is a Lig impiovement ovei the uataLase initializei options liom the +.1
anu +.2 ieleases that ieguiieu you to manually upuate the uataLase oi uiop anu iecieate
it when youi mouel changeu.
The pieLuilt solution still makes use ol the DropCreateDatabaseAlways initializei iathei
than using Coue Fiist Migiations. This allows us to ensuie the uataLase is ieset to a
well-known state Leloie you iun each example in this Look.
You can leain moie aLout Coue Fiist Migiations at http://b|ogs.nsdn.con/b/adonct/
archivc/2012/02/09/cj-1-3-rc|cascd.aspx.
The Model pioject ol the pieLuilt solution contains classes that make up the BAGA
uomain mouel. The BAGA mouel incluues a Destination class (Example 2-1) that iep-
iesents all the wonueilul places that oui intiepiu tiaveleis can ventuie to.
14 | Chapter 2:Querying with DbContext
Exanp|c 2-1. Dcstination c|ass as |istcd in down|oad so|ution
[Table("Locations", Schema = "baga")]
public class Destination
{
public Destination()
{
this.Lodgings = new List<Lodging>();
}
[Column("LocationID")]
public int DestinationId { get; set; }
[Required, Column("LocationName")]
[MaxLength(200)]
public string Name { get; set; }
public string Country { get; set; }
[MaxLength(500)]
public string Description { get; set; }
[Column(TypeName = "image")]
public byte[] Photo { get; set; }
public string TravelWarnings { get; set; }
public string ClimateInfo { get; set; }
public List<Lodging> Lodgings { get; set; }
}
The BAGA mouel also incluues a Lodging class (Example 2-2) that iepiesents the ac-
commouation that is availaLle at the vaiious Destinations.
Exanp|c 2-2. Lodging c|ass as |istcd in down|oad so|ution
public class Lodging
{
public int LodgingId { get; set; }
[Required]
[MaxLength(200)]
[MinLength(10)]
public string Name { get; set; }
public string Owner { get; set; }
public decimal MilesFromNearestAirport { get; set; }
[Column("destination_id")]
public int DestinationId { get; set; }
public Destination Destination { get; set; }
public List<InternetSpecial> InternetSpecials { get; set; }
public Nullable<int> PrimaryContactId { get; set; }
[InverseProperty("PrimaryContactFor")]
[ForeignKey("PrimaryContactId")]
public Person PrimaryContact { get; set; }
public Nullable<int> SecondaryContactId { get; set; }
[InverseProperty("SecondaryContactFor")]
[ForeignKey("SecondaryContactId")]
public Person SecondaryContact { get; set; }
}
Writing Queries with LINQ to Entities | 15
The Destination anu Lodging classes will Le useu extensively loi the examples thiough-
out this Look. To peiloim uata access using these classes you will Le using the BreakA
wayContext liom the DataAccess pioject. The pioject contains auuitional classes that
aie iepiesenteu in BreakAwayContext as well as the Lodgings anu Destinations. Ve`ll Le
using Coue Fiist loi the examples in this Look, Lut the technigues you will leain apply
to any context that ueiives liom DbContext. This incluues contexts cieateu using the
Mouel Fiist oi DataLase Fiist woikllows.
Exanp|c 2-3. Brca|AwayContcxt c|ass as |istcd in down|oad so|ution
public class BreakAwayContext : DbContext
{
public DbSet<Destination> Destinations { get; set; }
public DbSet<Lodging> Lodgings { get; set; }
public DbSet<Trip> Trips { get; set; }
public DbSet<Person> People { get; set; }
public DbSet<Reservation> Reservations { get; set; }
public DbSet<Payment> Payments { get; set; }
public DbSet<Activity> Activities { get; set; }
}
Querying All the Data from a Set
AiguaLly the simplest gueiy you can wiite is one that letches all the uata loi a given
entity type. This is the eguivalent ol a SELECT * FROM mytable gueiy in SQL. Foitunately
you uon`t neeu to know SQL, Lecause Entity Fiamewoik will take caie ol tianslating
LINQ gueiies into SQL loi you.
Getting all the uata liom a set uoesn`t ieguiie you to ieally wiite a gueiy. You can simply
iteiate ovei the contents ol any given DbSet anu Entity Fiamewoik will senu a gueiy to
the uataLase to linu all the uata in that set. Let`s auu a PrintAllDestinations methou
to oui console application that iteiates ovei the Destinations set uelineu in oui Break
AwayContext anu piints out the name ol each Destination (Example 2-+).
Exanp|c 2-1. Qucry jor a|| dcstinations
private static void PrintAllDestinations()
{
using (var context = new BreakAwayContext())
{
foreach (var destination in context.Destinations)
{
Console.WriteLine(destination.Name);
}
}
}
16 | Chapter 2:Querying with DbContext
Vhen you ueLug the application, the console winuow will close when
the application has linisheu executing, which may pievent you liom
inspecting the output. You can put a Lieakpoint at the enu ol the methou
loi ueLugging. Alteinatively, you can iun without ueLugging (CTRL -
F5), in which case Visual Stuuio will ensuie that the console winuow
iemains open altei the piogiam has linisheu executing.
Il you upuate the Main methou to call this new PrintAllDestinations methou anu iun
the application, you will see that the name ol each Destination in the uataLase is piinteu
to the console:
Grand Canyon
Hawaii
Wine Glass Bay
Great Barrier Reef
As the coue Legan iteiating ovei the contents ol the Destinations set, Entity Fiamewoik
issueu a SQL gueiy against the uataLase to loau the ieguiieu uata:
SELECT
[Extent1].[LocationID] AS [LocationID],
[Extent1].[LocationName] AS [LocationName],
[Extent1].[Country] AS [Country],
[Extent1].[Description] AS [Description],
[Extent1].[Photo] AS [Photo]
FROM [baga].[Locations] AS [Extent1]
The SQL may not look like the SQL you woulu have wiitten. This is Lecause Entity
Fiamewoik has a geneiic gueiy Luiluing algoiithm that not only cateis to this veiy
simple gueiy, Lut also loi much moie complex scenaiios.
The gueiy is sent to the uataLase when the liist iesult is ieguesteu Ly the application:
that`s uuiing the liist iteiation ol the foreach loop. Entity Fiamewoik uoesn`t pull Lack
all the uata at once, though. The gueiy iemains active anu the iesults aie ieau liom the
uataLase as they aie neeueu. By the time the foreach loop is completeu, all the iesults
have Leen ieau liom the uataLase.
One impoitant thing to note is that Entity Fiamewoik will gueiy the uataLase eveiy
time you tiiggei an iteiation ovei the contents ol a DbSet. This has peiloimance impli-
cations il you aie continually gueiying the uataLase loi the same uata. To avoiu this,
you can use a LINQ opeiatoi such as ToList to copy the iesults into a list. You can then
iteiate ovei the contents ol this list multiple times without causing multiple tiips to the
uataLase. Example 2-5 intiouuces a PrintAllDestinationsTwice methou that uemon-
stiates this appioach.
Exanp|c 2-5. |tcrating a|| Dcstinations twicc with onc databasc qucry
private static void PrintAllDestinationsTwice()
{
using (var context = new BreakAwayContext())
{
Querying All the Data from a Set | 17
var allDestinations = context.Destinations.ToList();
foreach (var destination in allDestinations)
{
Console.WriteLine(destination.Name);
}
foreach (var destination in allDestinations)
{
Console.WriteLine(destination.Name);
}
}
}
Because a gueiy is sent to the uataLase to linu the items in a DbSet, iteiating a DbSet
will only contain items that exist in the uataLase. Any oLjects that aie sitting in memoiy
waiting to Le saveu to the uataLase will not Le ietuineu. To ensuie auueu oLjects aie
incluueu you can use the technigues uesciiLeu in Queiying Local Data
on page 2+.
Using LINQ for Sorting, Filtering, and More
Vhile this chaptei will not Le an exhaustive list ol eveiything you can uo with LINQ
anu Entity Fiamewoik, let`s take a look at the patteins useu to achieve some common
gueiy tasks. Let`s say you want to piint out the names ol Destinations again, Lut this
time you want them oiueieu alphaLetically Ly Name. Auu a new PrintAllDestinations
Sorted methou that uses a LINQ gueiy to peiloim this soit (Example 2-6).
Exanp|c 2-. Qucry jor dcstinations sortcd by nanc
private static void PrintAllDestinationsSorted()
{
using (var context = new BreakAwayContext())
{
var query = from d in context.Destinations
orderby d.Name
select d;
foreach (var destination in query)
{
Console.WriteLine(destination.Name);
}
}
}
The aLove coue uses LINQ to cieate a gueiy anu then iteiates the iesults ol the gueiy
anu uisplays the name ol each uestination. The gueiy is expiesseu using a syntax that
looks a little Lit like SQL. You stait Ly telling it what you want to select liom (in oui
case, the Destinations set on oui context). You give the set a name so that you can ielei
to it thioughout the iest ol the gueiy (in oui case that name is d). Following this, you
18 | Chapter 2:Querying with DbContext
use opeiatois such as orderby, groupby, anu where to ueline the gueiy. Finally you spec-
ily what you want ietuineu using the select opeiatoi. In oui case we want the actual
Destination oLjects ietuineu, so we specily the name that we gave the set in the liist line.
RememLei that Entity Fiamewoik won`t execute the gueiy against the uataLase until
it neeus the liist iesult. Duiing the liist iteiation ol the foreach loop, the gueiy is sent
to the uataLase. The gueiy iemains active anu each iesult is ieau liom the uataLase as
it is neeueu Ly the application. LINQ also incluues methous that will copy the iesults
ol a gueiy into a collection. Foi example, ToList can Le calleu on a gueiy to copy the
iesults into a new List<T>. Calling a methou such as this will cause all the iesults to Le
ietiieveu liom the uataLase anu Le copieu into the new List<T>.
The coue shown in Example 2-6 uses the LINQ qucry syntax to expiess the gueiy.
Vhile most people linu this the easiest to unueistanu, theie is an alteinate ncthod
syntax that can Le useu il you pielei. Example 2-7 shows the same gueiy expiesseu
using methou syntax.
Exanp|c 2-7. L|NQ ncthod syntax in C=
var query = context.Destinations
.OrderBy(d => d.Name);
The methou syntax makes use ol lamLua expiessions to ueline the gueiy. The LINQ
methous aie stiongly typeu, which gives you IntelliSense anu compile-time checking
loi the lamLua expiessions you wiite. Foi example, in the OrderBy methou we aie using
a lamLua expiession to specily that we want to oiuei Ly the Name piopeity. You stait a
lamLua expiession Ly giving a name to the thing you aie opeiating on; this loims the
lelt siue ol the expiession. In oui case we aie opeiating on a Destination anu we have
chosen to call it d. Then, on the iight siue ol the expiession, you specily the Louy ol
the expiession. In oui case we just want to iuentily the Name piopeity.
C= uses the lamLua sign (=>) to sepaiate the lelt anu iight siues ol the expiession.
VB.NET uses the Function keywoiu lolloweu Ly Liackets to iuentily the lelt siue ol the
expiession. Example 2-S shows the same gueiy wiitten in VB.NET using the methou
syntax.
Exanp|c 2-8. L|NQ ncthod syntax in \B.NET
context.Destinations.OrderBy(Function(d) d.Name)
Anothei common task is to liltei the iesults ol a gueiy. Foi example, we may only want
Destinations liom Austialia. Auu the PrintAustralianDestinations methou shown in
Example 2-9.
Exanp|c 2-9. Qucry jor Austra|ian dcstinations
private static void PrintAustralianDestinations()
{
using (var context = new BreakAwayContext())
{
Using LINQ for Sorting, Filtering, and More | 19
var query = from d in context.Destinations
where d.Country == "Australia"
select d;
foreach (var destination in query)
{
Console.WriteLine(destination.Name);
}
}
}
This coue looks veiy similai to the PrintAllDestinationsSorted we saw in Exam-
ple 2-6, except we aie using the where opeiatoi insteau ol orderby. You can also comLine
these opeiatois. Example 2-10 shows how to gueiy loi Austialian Destinations soiteu
Ly name.
Exanp|c 2-10. Qucry conbining ji|tcr and sort
var query = from d in context.Destinations
where d.Country == "Australia"
orderby d.Name
select d;
Opeiatois can also Le comLineu in the methou syntax. The same gueiy liom Exam-
ple 2-10 is shown using methou syntax in Example 2-11.
Exanp|c 2-11. Mcthod syntax jor conbining ji|tcr and sort
var query = context.Destinations
.Where(d => d.Country == "Australia")
.OrderBy(d => d.Name);
So lai oui gueiies have ietuineu collections ol entities liom oui mouel, Lut this may
not always Le the case. In lact, we have Leen ietuining complete Destination oLjects
when we ieally only neeu the name. You can use projcction to cieate a gueiy that selects
liom a set ol entities in youi mouel Lut ietuins iesults that aie ol a uilleient type. Foi
example, you can use piojection to cieate a gueiy that selects liom a set ol entities type
Lut only ietuins a suLset ol the piopeities ol that entity. It`s calleu piojection Lecause
you aie piojecting uata liom the shape ol the souice that you aie selecting liom onto
the shape ol the iesult set you want.
In oui case we want to pioject a gueiy aLout Destinations into a iesult set that just has
a stiing iepiesenting the uestination`s name. Example 2-12 auus a PrintDestination
NameOnly methou that shows how we use the select section ol oui gueiy to specily what
we want the iesult set to contain.
Exanp|c 2-12. Qucrying jor just thc Dcstination nanc
private static void PrintDestinationNameOnly()
{
using (var context = new BreakAwayContext())
{
20 | Chapter 2:Querying with DbContext
var query = from d in context.Destinations
where d.Country == "Australia"
orderby d.Name
select d.Name;
foreach (var name in query)
{
Console.WriteLine(name);
}
}
}
Example 2-13 shows how this same gueiy can Le wiitten using methou syntax Ly mak-
ing use ol the Select methou.
Exanp|c 2-13. Mcthod syntax jor projcction
var query = context.Destinations
.Where(d => d.Country == "Australia")
.OrderBy(d => d.Name)
.Select(d => d.Name);
LINQ is a poweilul gueiy language anu this section has just giazeu the suilace ol its
capaLilities. Progranning Entity Irancwor|, 2c, contains a much ueepei look into
using LINQ with the Entity Fiamewoik. Theie aie also moie example gueiies availaLle
in the Entity Fiamewoik MSDN uocumentation: http://nsdn.nicrosojt.con/cn-us/|i
brary/bb39937.aspx.
Finding a Single Object
So lai you`ve seen gueiies that ietuin a collection ol entities, Lut sometimes you will
want to iun a gueiy that just ietuins a single oLject. The most common scenaiio loi
gueiying loi a single oLject is to linu the oLject with a given key. The DLContext API
makes this veiy simple Ly exposing a Find methou on DbSet. Find accepts the value to
Le seaicheu loi anu will ietuin the coiiesponuing oLject il it is lounu. Il theie is no
entity with the pioviueu key, Find will ietuin null.
One ol the gieat things aLout Find is that it uoesn`t unnecessaiily gueiy the uataLase.
It`s also capaLle ol linuing newly auueu oLjects that haven`t yet Leen saveu to the
uataLase. Find uses a simple set ol iules to locate the oLject (in oiuei ol pieceuence):
1. Look in memoiy loi an existing entity that has Leen loaueu liom the uataLase oi
attacheu to the context (you`ll leain moie aLout attaching oLjects in Chaptei +).
2. Look at auueu oLjects that have not yet Leen saveu to the uataLase.
3. Look in the uataLase loi entities that have not yet Leen loaueu into memoiy.
To see this Lehavioi, auu the FindDestination methou shown in Example 2-1+. This
methou accepts an ID liom the usei anu then attempts to locate the Destination with
the specilieu ID.
Finding a Single Object | 21
Exanp|c 2-11. Using Iind to |ocatc a Dcstination
private static void FindDestination()
{
Console.Write("Enter id of Destination to find: ");
var id = int.Parse(Console.ReadLine());
using (var context = new BreakAwayContext())
{
var destination = context.Destinations.Find(id);
if (destination == null)
{
Console.WriteLine("Destination not found!");
}
else
{
Console.WriteLine(destination.Name);
}
}
}
The coue aLove uses the Find methou to look up the Destination with the specilieu ID.
Il one is lounu, it piints out the name ol the uestination. Il Find ietuins null, inuicating
theie is no Destination with the specilieu ID, an eiioi message is uisplayeu to the usei.
Find with Composite Keys
Entity Fiamewoik suppoits entities that have conpositc |cys, that is, entities wheie the
key is maue up ol two oi moie piopeities. Foi example, you may have a Passport entity
that uses a comLination ol IssuingCountry anu PassportNumber as its key. To locate
entities with a composite key you supply each ol the key values to Find:
context.Passports.Find("USA", "123456789")
The key values must Le supplieu in the same oiuei that they appeai in the mouel. Il
you aie using Mouel Fiist oi DataLase Fiist, this is the oiuei that they appeai in the
uesignei. Vhen composite keys aie useu, Coue Fiist ieguiies you to specily an oiuei
loi them. You can use the Column annotation with the Order paiametei to specily the
oiuei. Il you aie using the Fluent API, a HasKey call is ieguiieu; the oiuei ol the key
piopeities is the oiuei they appeai in the Louy ol the HasKey call.
Theie may Le times when you want to gueiy loi a single oLject Lut aie not aLle to use
Find. These coulu incluue wanting to gueiy Ly something othei than the key oi wanting
to incluue ielateu uata in the gueiy (as uesciiLeu in Eagei Loauing on page 33). To
uo this, you will neeu to cieate a stanuaiu LINQ gueiy anu then use the Single methou
to get a single oLject as the iesult.
Let`s say we want to locate the Destination that has the name Gieat Baiiiei Reel.
Name isn`t the key ol Destination Lut we know theie is, anu only evei will Le, one Gieat
Baiiiei Reel. Example 2-10 intiouuces a FindGreatBarrierReef methou that will locate
this single Destination.
22 | Chapter 2:Querying with DbContext
Exanp|c 2-15. Qucry jor sing|c cntity bascd on nanc
private static void FindGreatBarrierReef()
{
using (var context = new BreakAwayContext())
{
var query = from d in context.Destinations
where d.Name == "Great Barrier Reef"
select d;
var reef = query.Single();
Console.WriteLine(reef.Description);
}
}
The LINQ gueiy looks the same as any othei gueiy that lilteis Laseu on name. Ve then
use the Single methou to let Entity Fiamewoik know that we expect a single iesult. Il
the gueiy ietuins no iesults, oi moie than one iesult, an exception will Le thiown. Il
theie aie potentially no matches, you can use the SingleOrDefault methou, which will
ietuin null il no iesults aie lounu. Example 2-16 shows the FindGreatBarrierReef
methou upuateu to account loi the lact it may not exist in the uataLase.
Exanp|c 2-1. Qucry jor sing|c cntity that nay not cxist
private static void FindGreatBarrierReef()
{
using (var context = new BreakAwayContext())
{
var query = from d in context.Destinations
where d.Name == "Great Barrier Reef"
select d;
var reef = query.SingleOrDefault();
if (reef == null)
{
Console.WriteLine("Can't find the reef!");
}
else
{
Console.WriteLine(reef.Description);
}
}
}
SingleOrDefault uses the same uataLase gueiy that Find uses when it looks loi entities
in the uataLase. The SQL selects the TOP two iesults so that it can ensuie theie is only
one match:
SELECT TOP (2)
[Extent1].[LocationID] AS [LocationID],
[Extent1].[LocationName] AS [LocationName],
[Extent1].[Country] AS [Country],
Finding a Single Object | 23
[Extent1].[Description] AS [Description],
[Extent1].[Photo] AS [Photo],
[Extent1].[TravelWarnings] AS [TravelWarnings],
[Extent1].[ClimateInfo] AS [ClimateInfo]
FROM [baga].[Locations] AS [Extent1]
WHERE N'Great Barrier Reef' = [Extent1].[LocationName]
Il two iows aie lounu, Single anu SingleOrDefault will thiow Lecause theie is not a
single iesult. Il you just want the liist iesult, anu aien`t conceineu il theie is moie than
one iesult, you can use First oi FirstOrDefault.
One impoitant thing to iememLei is that LINQ gueiies against a DbSet always senu a
gueiy to the uataLase to linu the uata. So, il the Gieat Baiiiei Reel was a newly auueu
Destination that haun`t Leen saveu to the uataLase yet, the gueiies in Example 2-15
anu Example 2-16 won`t Le aLle to locate it. To look loi newly auueu entities, you
woulu also neeu to gueiy the in-memoiy uata using the technigues shown in Queiying
Local Data on page 2+.
Querying Local Data
So lai you`ve useu LINQ to gueiy a DbSet uiiectly, which always iesults in a SQL gueiy
Leing sent to the uataLase to loau the uata. You`ve also useu the Find methou, which
will look loi in-memoiy uata Leloie gueiying that uataLase. Find will only gueiy Laseu
on the key piopeity though, anu theie may Le times when you want to use a moie
complex gueiy against uata that is alieauy in memoiy anu Leing tiackeu Ly youi
DbContext.
One ol the ieasons you may want to uo this is to avoiu senuing multiple gueiies to the
uataLase when you know that all the uata you neeu is alieauy loaueu into memoiy.
Back in Example 2-5, we saw one way to uo this was to use ToList to copy the iesults
ol a gueiy into a list. Vhile this woiks well il we aie using the uata within the same
Llock ol coue, things get a little messy il we neeu to stait passing that list aiounu oui
application. Foi example, we might want to loau all Destinations liom the uataLase
when oui application loaus. Dilleient aieas ol oui application aie then going to want
to iun uilleient gueiies against that uata. In some places we might want to uisplay all
Destinations, in otheis we might want to soit Ly Name, anu in otheis we might want
to liltei Ly Country. Rathei than passing aiounu a list ol Destination oLjects, we can
take auvantage ol the lact that oui context is tiacking all the instances anu gueiy its
local uata.
Anothei ieason may Le that you want the iesults to incluue newly auueu uata, which
uoesn`t yet exist in the uataLase. Using ToList on a LINQ gueiy against a DbSet will
always senu a gueiy to the uataLase. This means that any new oLjects that uon`t yet
exist in the uataLase won`t Le incluueu in the iesults. Local gueiies, howevei, will
incluue newly cieateu oLjects in the iesults.
24 | Chapter 2:Querying with DbContext
The in-memoiy uata loi a DbSet is availaLle via the Local piopeity. Local will ietuin all
the uata that has Leen loaueu liom the uataLase plus any newly auueu uata. Any uata
that has Leen maikeu as ueleteu Lut hasn`t Leen ueleteu liom the uataLase yet will Le
lilteieu out loi you. Moie inloimation on how entities get into these uilleient states is
availaLle in Chaptei 3.
Let`s stait with the veiy simple task ol linuing out how many Destinations aie in mem-
oiy anu availaLle to Le gueiieu. Go aheau anu auu the GetLocalDestinationCount
methou, as shown in Example 2-17.
Exanp|c 2-17. Chcc|ing how nany Dcstinations arc in-ncnory
private static void GetLocalDestinationCount()
{
using (var context = new BreakAwayContext())
{
var count = context.Destinations.Local.Count;
Console.WriteLine("Destinations in memory: {0}", count);
}
}
The coue accesses the Local piopeity ol the Destinations set that we cieateu on oui
BreakAwayContext. Rathei than iunning a gueiy, we simply stoie the count in a vaiiaLle
anu then piint it to the console. Il you iun the application you will see that the count
is zeio:
Destinations in memory: 0
Ve`ie getting a zeio count Lecause we haven`t iun any gueiies to loau Destinations
liom the uataLase, anu we haven`t auueu any new Destination oLjects eithei. Let`s
upuate the GetLocalDestinationCount methou to gueiy some uata liom the uataLase
Leloie getting the local count (Example 2-1S).
Exanp|c 2-18. Chcc|ing in-ncnory data ajtcr a qucry
private static void GetLocalDestinationCount()
{
using (var context = new BreakAwayContext())
{
foreach (var destination in context.Destinations)
{
Console.WriteLine(destination.Name);
}
var count = context.Destinations.Local.Count;
Console.WriteLine("Destinations in memory: {0}", count);
}
}
This new coue iteiates ovei the Destinations set, causing the uata to Le loaueu liom
the uataLase. Because the uata is loaueu when we get the count liom the Local piopeity,
we now see a nonzeio iesult when we iun the application:
Querying Local Data | 25
Grand Canyon
Hawaii
Wine Glass Bay
Great Barrier Reef
Destinations in memory: 4
Using the Load Method to Bring Data into Memory
Iteiating ovei the contents ol a DbSet with a foreach loop is one way to get all the uata
into memoiy, Lut it`s a little inellicient to uo that just loi the sake ol loauing uata. It`s
also a little uncleai what the intent ol the coue is, especially il the iteiation coue uoesn`t
uiiectly pieceue the local gueiy.
Foitunately the DLContext API incluues a Load methou, which can Le useu on a
DbSet to pull all the uata liom the uataLase into memoiy. Go aheau anu auu the GetLo
calDestinationCountWithLoad methou (Example 2-19) that uses Load on the Destina
tions set anu then piints out the count ol in-memoiy Destinations.
Exanp|c 2-19. Using thc Load to bring data into ncnory
private static void GetLocalDestinationCountWithLoad()
{
using (var context = new BreakAwayContext())
{
context.Destinations.Load();
var count = context.Destinations.Local.Count;
Console.WriteLine("Destinations in memory: {0}", count);
}
}
Compaie this coue with the GetLocalDestinationCount methou we wiote Lack in Ex-
ample 2-1S. This upuateu coue makes it much cleaiei that oui intent is to loau the
contents ol the Destinations set anu then gueiy the in-memoiy uata.
Load is actually an extension methou on IQueryable<T> anu is uelineu in
the System.Data.Entity namespace. Il you want to use Load, you will
neeu to have this namespace impoiteu.
Because Load is an extension methou on IQueryable<T>, we can also use it to loau the
iesults ol a LINQ gueiy into memoiy, iathei than the entiie contents ol a set. Foi
example, let`s say we only wanteu to loau Austialian Destinations into memoiy anu
then iun a lew local gueiies on that suLset ol uata. Let`s auu the LoadAustralianDesti
nations methou shown in Example 2-20.
Exanp|c 2-20. Loading rcsu|ts oj a L|NQ qucry into ncnory
private static void LoadAustralianDestinations()
{
26 | Chapter 2:Querying with DbContext
using (var context = new BreakAwayContext())
{
var query = from d in context.Destinations
where d.Country == "Australia"
select d;
query.Load();
var count = context.Destinations.Local.Count;
Console.WriteLine("Aussie destinations in memory: {0}", count);
}
}
This time just the Destinations with Country set to Austialia aie loaueu into memoiy.
Vhen we iun the application, we see that the count we get liom Local is ieuuceu to
iellect this.
Using Load on a LINQ gueiy will Liing the iesults ol that gueiy into
memoiy Lut it uoes not iemove the iesults ol pievious gueiies. Foi ex-
ample il you calleu Load on a gueiy loi Austialian uestinations anu then
Load on a gueiy loi Ameiican uestinations, Loth Austialian anu Amei-
ican uestinations woulu Le in memoiy anu woulu Le ietuineu liom
Local.
Running LINQ Queries Against Local
So lai we have just lookeu at getting the count liom Local to make suie that it is ie-
tuining the coiiect uata that we Liought into memoiy. Because Local is just a collection
ol in-memoiy oLjects, we can also iun gueiies against it. One ol the gieat things aLout
LINQ is that it`s not specilic to Entity Fiamewoik. Ve can use the same LINQ syntax
to gueiy a numLei ol uilleient uata souices, incluuing in-memoiy collections ol oLjects.
Let`s auu a LocalLinqQueries methou that pulls uata into memoiy using a single uata-
Lase gueiy anu then iuns some in-memoiy gueiies using Local (Example 2-21).
Exanp|c 2-21. Using L|NQ to qucry Loca|
private static void LocalLinqQueries()
{
using (var context = new BreakAwayContext())
{
context.Destinations.Load();
var sortedDestinations = from d in context.Destinations.Local
orderby d.Name
select d;
Console.WriteLine("All Destinations:");
foreach (var destination in sortedDestinations)
{
Console.WriteLine(destination.Name);
Querying Local Data | 27
}
var aussieDestinations = from d in context.Destinations.Local
where d.Country == "Australia"
select d;
Console.WriteLine();
Console.WriteLine("Australian Destinations:");
foreach (var destination in aussieDestinations)
{
Console.WriteLine(destination.Name);
}
}
}
The coue loaus all Destinations into memoiy anu then iuns one gueiy to soit them Ly
Name anu anothei to pull out just the Austialian Destinations. RememLei that Find also
uelaults to using in-memoiy uata wheie possiLle. So we coulu also use Find anu it woulu
use the uata we loaueu iathei than senuing moie gueiies to the uataLase.
Vhile Load anu Local aie gieat il you want to ieuuce the numLei ol gueiies that get
iun against the uataLase just iememLei that pulling all youi uata into memoiy may Le
an expensive opeiation. Il you aie iunning multiple gueiies that only ietuin a suLset
ol youi uata you`ll pioLaLly get Lettei peiloimance Ly letting these gueiies hit the
uataLase anu just pull Lack the uata you actually neeu.
Differences Between LINQ Providers
Theie aie a lew suLtle Lut impoitant uilleiences Letween gueiying uiiectly against a
DbSet anu against Local. These two uata souices actually use two uilleient LINQ pio-
viueis. Queiying against DbSet uses LINQ to Entities, which is specilic to Entity Fiame-
woik anu uses youi mouel anu mapping to tuin youi gueiy into SQL that is executeu
in the uataLase. Howevei, gueiying against Local uses LINQ to OLjects, which pei-
loims lilteiing, soiting, anu similai opeiations in memoiy using the stanuaiu .NET
opeiatois loi testing eguality, ueteimining oiueiing, anu the like.
The same gueiy syntax can ietuin uilleient iesults uepenuing on which one you aie
using. Foi example, the uataLase is typically not case-sensitive when compaiing stiing
values, Lut .NET is. Il you issueu a gueiy loi Destination names that contain gieat,
the uataLase woulu ietuin Gieat Baiiiei Reel anu The gieat wall ol China. The
same gueiy against Local woulu ietuin The gieat wall ol China Lut woulu not ietuin
Gieat Baiiiei Reel Lecause the capitalization ol gieat is uilleient.
Most LINQ pioviueis suppoit the same coie leatuies, Lut theie aie some uilleiences
in leatuies Letween each pioviuei. Foi example, LINQ to OLjects suppoits the Last
opeiatoi Lut LINQ to Entities uoes not. Theieloie, you can use Last when iunning
gueiies against Local Lut not when iunning gueiies uiiectly against a DbSet.
28 | Chapter 2:Querying with DbContext
Working with the ObservableCollection Returned by Local
Il you`ve lookeu at the API closely you may have noticeu that Local ietuins an Observ
ableCollection<TEntity>. This type ol collection allows suLsciiLeis to Le notilieu
whenevei oLjects aie auueu oi iemoveu liom the collection. ObservableCollection is
uselul in a numLei ol uata-Linuing scenaiios, Lut it can also Le uselul il youi application
neeus to know when new uata comes into memoiy.
Local will iaise the CollectionChanged event whenevei the contents ol Local change.
This can Le when uata is Liought Lack liom that uataLase via a gueiy, when new oLjects
aie auueu to the DbContext, oi when oLjects pieviously Liought into memoiy aie
maikeu loi ueletion.
Let`s auu a ListenToLocalChanges methou that uses this lunctionality to log any changes
to Destinations.Local to the console (Example 2-22).
Exanp|c 2-22. Using Co||cctionChangcd to print out changcs to Loca|
private static void ListenToLocalChanges()
{
using (var context = new BreakAwayContext())
{
context.Destinations.Local
.CollectionChanged += (sender, args) =>
{
if (args.NewItems != null)
{
foreach (Destination item in args.NewItems)
{
Console.WriteLine("Added: " + item.Name);
}
}
if (args.OldItems != null)
{
foreach (Destination item in args.OldItems)
{
Console.WriteLine("Removed: " + item.Name);
}
}
};
context.Destinations.Load();
}
}
The coue auus a new event hanulei to the Local collection ol Destinations. This hanulei
looks at items enteiing oi leaving the collection anu piints out the name ol the allecteu
Destination anu inuicates il it is Leing auueu oi iemoveu. Once the event hanulei is in
place, we use Load to pull all the uata liom the uataLase into memoiy. Il you iun the
application, you can see the output appeaiing as items aie ietuineu liom the uataLase:
Querying Local Data | 29
Added: Grand Canyon
Added: Hawaii
Added: Wine Glass Bay
Added: Great Barrier Reef
These events coulu Le hanuy il you have a scieen that neeus to Le ieliesheu whenevei
some uata in youi context changes. Foi example, you might have a scieen that uisplays
all Destinations anu anothei scieen wheie the usei can auu a new Destination. You
coulu wiie up the scieen uisplaying all Destinations to listen to the Collection
Changed event anu ieliesh whenevei anything is auueu oi iemoveu.
Some UI liamewoiks, such as VPF, will take caie ol this loi you so that you uon`t have
to wiite coue to listen to changes. Il you Linu a VPF ListBox to the contents ol
Local, whenevei any othei aiea ol the application auus oi iemoves an entity liom the
DbSet, the ListBox will Le upuateu to iellect those changes.
Il you use LINQ to gueiy the contents ol Local, the iesult ol the gueiy
is no longei an ObservableCollection. This means il you iun a LINQ
gueiy against Local anu Linu the iesults to a VPF ListBox, it will no
longei get automatically upuateu loi you when entities aie auueu oi
iemoveu. You woulu neeu to wiite coue that listens to OnCollection
Changed on DbSet.Local anu ieiun the gueiy to ieliesh the ListBox.
Loading Related Data
So lai we have lookeu at accessing uata loi a single type ol entity anu eveiything has
Leen aLout Destinations. But il we weie wiiting a ieal application, we woulu pioLaLly
want to know something aLout the Lodging that is availaLle at each Destination. Il we
want to access the Lodgings associateu with a Destination, that means woiking with
ielateu uata.
You`ll neeu to pull ielateu uata into memoiy so that we can look at it. Theie aie thiee
appioaches you can use to loau ielateu uata: lazily, eageily, oi explicitly. Vhile they
may achieve the same enu iesult, theie aie some uilleiences Letween each appioach
that can have a signilicant impact on peiloimance. This isn`t a one-time uecision eithei.
Dilleient appioaches may Le Lettei at uilleient times. This section will walk thiough
the thiee availaLle options anu help you woik out which one is Lest loi you in uilleient
situations.
The Demystilying Entity Fiamewoik Stiategies: Loauing Relateu
Data MSDN aiticle gives a uetaileu look at the pios anu cons ol the
uilleient stiategies anu some pointeis on choosing the iight stiategy loi
you.
30 | Chapter 2:Querying with DbContext
Lazy Loading
Lazy loauing ielateu uata is the most tianspaient to youi application anu involves let-
ting Entity Fiamewoik automatically ietiieve the ielateu uata loi you when you tiy to
access it. Foi example, you may have the Gianu Canyon uestination loaueu. Il you then
use the Lodgings piopeity ol this Destination, Entity Fiamewoik will automatically
senu a gueiy to the uataLase to loau all Lodgings at the Gianu Canyon. It will appeai
to youi application coue as il the Lodgings piopeity was always populateu.
Entity Fiamewoik achieves lazy loauing using a dynanic proxy. Heie`s how that woiks.
Vhen Entity Fiamewoik ietuins the iesults ol a gueiy, it cieates instances ol youi
classes anu populates them with the uata that was ietuineu liom the uataLase. Entity
Fiamewoik has the aLility to uynamically cieate a new type at iuntime that ueiives
liom youi POCO class. This new class acts as a pioxy to youi POCO class anu is ieleiieu
to as a uynamic pioxy. It will oveiiiue the navigation piopeities ol youi POCO class
anu incluue some auuitional logic to ietiieve the uata liom the uataLase when the
piopeity is accesseu. Because the uynamic pioxy ueiives liom youi POCO class, youi
application can Le wiitten in teims ol the POCO class anu uoesn`t neeu to Le awaie
that theie may Le a uynamic pioxy at iuntime.
DbContext has a conliguiation setting that enaLles lazy loauing: DbCon
text.Configuration.LazyLoadingEnabled. This setting is true Ly uelault
anu theieloie il you have not changeu the uelault, the uynamic pioxy
will peiloim lazy loauing.
In oiuei to use uynamic pioxies, anu theieloie lazy loauing, theie aie a couple ol ciiteiia
youi class must meet. Il these ciiteiia aie not met, Entity Fiamewoik will not cieate a
uynamic pioxy loi the class anu will just ietuin instances ol youi POCO class, which
cannot peiloim lazy loauing:
Youi POCO class must Le puLlic anu not sealeu.
The navigation piopeities that you want to Le lazy loaueu must also Le maikeu as
virtual (Overridable in Visual Basic) so that Entity Fiamewoik can oveiiiue the
piopeities to incluue the lazy loauing logic.
Beloie we make any changes to oui classes, let`s see what the Lehavioi is like without
uynamic pioxies. Auu a TestLazyLoading methou that attempts to access the Lodg
ings associateu with a specilic Destination (Example 2-23).
Exanp|c 2-23. Mcthod to acccss rc|atcd data
private static void TestLazyLoading()
{
using (var context = new BreakAwayContext())
{
var query = from d in context.Destinations
where d.Name == "Grand Canyon"
Loading Related Data | 31
select d;
var canyon = query.Single();
Console.WriteLine("Grand Canyon Lodging:");
if (canyon.Lodgings != null)
{
foreach (var lodging in canyon.Lodgings)
{
Console.WriteLine(lodging.Name);
}
}
}
}
The coue locates the Gianu Canyon Destination anu then tests il the Lodgings piopeity
is populateu. Il it is populateu, the name ol each associateu Lodging is piinteu to the
console. Il you upuate the Main methou to call TestLazyLoading anu iun the application,
you will see that nothing is piinteu out to the console. This is Lecause the Lodgings
piopeity on Destination isn`t maikeu as virtual (Overridable in Visual Basic), so Entity
Fiamewoik can`t oveiiiue the piopeity in a uynamic pioxy. Entity Fiamewoik is loiceu
to use youi implementation ol the piopeity (that uoesn`t peiloim lazy loauing) iathei
than ieplacing it with an implementation that incluues the lazy loauing logic. Let`s go
aheau anu euit the Destination class so that the piopeity is maikeu as virtual:
public virtual List<Lodging> Lodgings { get; set; }
Now Entity Fiamewoik can cieate a uynamic pioxy loi the Destination class. Il you
iun the application again, you`ll see that the inuiviuual Lodgings loi the Gianu Canyon
aie uisplayeu Lecause the uata was automatically loaueu loi you when the coue en-
counteieu the liist ieguest loi Lodgings:
Grand Canyon Lodging:
Grand Hotel
Dave's Dump
As the coue executeu, Entity Fiamewoik sent two gueiies to the uataLase (Fig-
uie 2-1). The liist gueiy ietiieves the uata loi the Gianu Canyon Destination anu was
executeu when the coue calleu the Single methou on query. RememLei that the Sin
gle methou uses a SELECT TOP (2) gueiy to ensuie theie is one iesult anu only one
iesult. The seconu gueiy selects all Lodgings associateu with the Gianu Canyon. This
gueiy was sent at the moment the coue liist tiieu to access the Lodgings piopeity loi
the Gianu Canyon Destination.
Iigurc 2-1. Lazy |oading qucry
32 | Chapter 2:Querying with DbContext
Multiple Active Result Sets
Vhen Entity Fiamewoik iuns a gueiy against the uataLase, it uoesn`t Liing all the uata
Lack the liist time you ieau uata liom the gueiy. Each iow ol uata is tiansleiieu liom
the uataLase as it is neeueu. This means that as you iteiate ovei the iesults ol a gueiy,
the gueiy is still active anu uata is Leing pulleu Lack liom the uataLase as you iteiate.
Vhen lazy loauing is Leing useu, it`s veiy common loi lazy loauing to occui while you
aie iteiating the iesults ol a gueiy. Foi example, you may have gueiieu loi all Destina
tions. You might then iteiate ovei the iesults using a foreach loop. Insiue the loop you
might access the Lodgings piopeity, which will Le lazy loaueu liom the uataLase. This
means that the gueiy to loau Lodgings is executeu while the main gueiy to letch all
Destinations is still active.
Multiple Active Result Sets (MARS) is a SQL Seivei leatuie that allows moie than one
active gueiy against the same connection. Vhen Coue Fiist cieates a connection Ly
convention, which it has Leen in oui examples, it will enaLle MARS. Il you aie supplying
youi own connection, you will neeu to ensuie that MARS is enaLleu il you want to Le
aLle to have multiple active gueiies.
Il you uon`t enaLle MARS anu youi coue tiies to iun two active gueiies, you will ieceive
an exception. The exception you ieceive will uepenu on the opeiation that tiiggeis the
seconu gueiy, Lut the innei exception will Le an InvalidOperationException stating
Theie is alieauy an open DataReauei associateu with this Commanu which must Le
closeu liist.
Understanding the downsides of lazy loading
Lazy loauing is veiy simple Lecause youi application uoesn`t ieally neeu to Le awaie
that uata is Leing loaueu liom the uataLase. But that is also one ol its uangeis! Impiopei
use ol lazy loauing can iesult in a lot ol gueiies Leing sent to the uataLase. Foi example,
you might loau lilty Destinations anu then access the Lodgings piopeity on each. That
woulu iesult in 51 gueiies against the uataLaseone gueiy to get the Destinations anu
then loi each ol the lilty Destinations, to loau that Destination`s Lodgings. In cases like
this it may Le much moie ellicient to loau all that uata in a single gueiy, using a SQL
join in the uataLase gueiy. This is wheie eagei loauing comes into play.
Il you ueciue that lazy loauing is just too much magic, you can choose
to uisaLle it altogethei Ly using the DbContext.Configuration.LazyLoa
dingEnabled piopeity. Il this switch is set to false, lazy loauing will nevei
occui, even il a navigation piopeity is maikeu as virtual.
Eager Loading
Eagei loauing ielateu uata ielies on you telling Entity Fiamewoik what ielateu uata to
incluue when you gueiy loi an entity type. Entity Fiamewoik will then use a JOIN in
Loading Related Data | 33
the geneiateu SQL to pull Lack all ol the uata in a single gueiy. Let`s assume we want
to iun though all Destinations anu piint out the Lodgings loi each. Auu a TestEager
Loading methou that gueiies loi all Destinations anu uses Include to also gueiy loi the
associateu Lodgings (Example 2-2+).
Exanp|c 2-21. Using cagcr |oading to |oad rc|atcd data
private static void TestEagerLoading()
{
using (var context = new BreakAwayContext())
{
var allDestinations = context
.Destinations
.Include(d => d.Lodgings);
foreach (var destination in allDestinations)
{
Console.WriteLine(destination.Name);
foreach (var lodging in destination.Lodgings)
{
Console.WriteLine(" - " + lodging.Name);
}
}
}
}
The coue uses the Include methou to inuicate that the gueiy loi all uestinations shoulu
incluue the ielateu Lodging uata. Include uses a lamLua expiession to specily which
piopeities to incluue the uata loi. Vhen the application iuns, we see a single gueiy is
executeu against the uataLase (Figuie 2-2). This gueiy uses a join to ietuin the Desti
nation anu Lodging uata as a single iesult set.
Iigurc 2-2. Eagcr |oading rcturns a|| data in a sing|c qucry
Theie is also a stiing-Laseu oveiloau ol Include that just accepts the name ol the piop-
eity to incluue uata loi (Include(Lodgings) in oui case). Pievious veisions ol Entity
Fiamewoik only incluueu this stiing option. The stiing-Laseu oveiloau is pioLlematic
Lecause it`s not stiongly typeu anu theieloie theie is no compile-time checking ol the
paiametei. This can leau to issues with mistypeu piopeity names oi lailing to upuate
the Incluue call il the piopeity is ienameu in the lutuie.
The lamLua veision ol the Include methou is uelineu as an extension
methou in System.Data.Entity. To use the lamLua oveiloau you will
neeu to impoit this namespace.
34 | Chapter 2:Querying with DbContext
It is possiLle to incluue moie than one ielateu set ol uata in a single gueiy. Say we
wanteu to gueiy loi Lodgings anu incluue the PrimaryContact plus the associateu
Photo. Ve uo this Ly uotting thiough the navigation piopeities in the lamLua
expiession:
context.Lodgings
.Include(l => l.PrimaryContact.Photo)
The syntax gets a little moie complicateu il you have a collection navigation piopeity
in the miuule ol the path to Le incluueu. Vhat il you want to gueiy loi Destinations
anu incluue Lodgings anu also the PrimaryContact loi each ol the ielateu Lodging in-
stances? Following the collection, you neeu to use the LINQ Select methou to iuentily
which piopeity you want to loau:
context.Destinations
.Include(d => d.Lodgings.Select(l => l.PrimaryContact))
Include can Le useu multiple times in the same gueiy to iuentily uilleient uata to Le
loaueu. Foi example, you may want to gueiy the Lodgings set anu incluue Loth Pri
maryContact anu SecondaryContact. This ieguiies two sepaiate calls to Include:
context.Lodgings
.Include(l => l.PrimaryContact)
.Include(l => l.SecondaryContact)
Eagei loauing is cuiiently only aLle to incluue the entiie contents ol a
navigation piopeity. The aLility to only incluue a suLset ol the contents
ol a collection navigation piopeity is a common ieguest, Lut it is not
cuiiently suppoiteu Ly the Entity Fiamewoik.
Understanding the downsides of eager loading
One thing to Leai in minu with eagei loauing is that lewei gueiies aien`t always Lettei.
The ieuuction in the numLei ol gueiies comes at the expense ol the simplicity ol the
gueiies Leing executeu. As you incluue moie anu moie uata, the numLei ol joins in the
gueiy that is sent to the uataLase incieases anu iesults in a slowei anu moie complex
gueiy. Il you neeu a signilicant amount ol ielateu uata, multiple simplei gueiies will
olten Le signilicantly lastei than one Lig gueiy that ietuins all the uata.
Using Include in LINQ queries
You can also use Include as pait ol a LINQ gueiy Ly auuing the Include methou to the
DbSet Leing gueiieu. Il you aie using gueiy syntax, the Include goes in the from pait ol
the gueiy:
var query = from d in context.Destinations.Include(d => d.Lodgings)
where d.Country == "Australia"
select d;
Loading Related Data | 35
Il you aie using methou syntax, you can simply put Incluue in line with the othei
methou calls:
var query = context.Destinations
.Include(d => d.Lodgings)
.Where(d => d.Country == "Australia");
Include is uelineu as an extension methou on IQueryable<T> anu can theieloie Le auueu
to a gueiy at any point. It uoesn`t have to immeuiately lollow the DbSet liom which
you aie selecting. Foi example, you can call Incluue on an existing gueiy loi Austialian
Destinations to specily that Lodgings shoulu also Le incluueu:
var query = from d in context.Destinations
where d.Country == "Australia"
select d;
query = query.Include(d => d.Lodgings);
Note that the coue uoesn`t just call Include on the existing gueiy Lut oveiiiues the
query vaiiaLle with the iesult ol the Include call. This is necessaiy Lecause Include
uoesn`t mouily the gueiy that it is calleu on, it ietuins a new gueiy that will incluue
the ielateu uata. RememLei that Entity Fiamewoik uoesn`t execute any gueiies until
the coue uses the iesults ol the gueiy. The aLove coue uoesn`t use the iesults ol the
gueiy, so nothing will Le executeu against the uataLase until some othei coue accesses
the Destinations liom the query vaiiaLle.
Although Include is uelineu as an extension methou on IQueryable<T>
it will only have an ellect when useu on a LINQ to Entities gueiy. Il
anothei LINQ pioviuei is Leing useu, Include will have no ellect unless
the implementation ol IQueryable<T> exposes an Include methou that
accepts a single stiing paiametei. Il this methou exists, it will Le calleu
with a stiing iepiesenting the piopeity path that was specilieu to Le
incluueu.
Explicit Loading
Anothei loauing option is cxp|icit |oading. Explicit loauing is like lazy loauing in that
ielateu uata is loaueu sepaiately, altei the main uata has Leen loaueu. Howevei, unlike
lazy loauing, it uoesn`t automatically happen loi you; you neeu to call a methou to loau
the uata.
Theie aie a numLei ol ieasons you might opt loi explicit loauing ovei lazy loauing:
It iemoves the neeu to maik youi navigation piopeities as viitual. To some this
may seem like a tiivial change, loi otheis, the lact that a uata access technology
ieguiies you to change youi POCO classes is lai liom iueal.
You may Le woiking with an existing class liLiaiy wheie the navigation piopeities
aie not maikeu as viitual anu you simply can`t change that.
36 | Chapter 2:Querying with DbContext
Explicit loauing allows you to Le suie that you know exactly when gueiies aie sent
to the uataLase. Lazy loauing has the potential to geneiate a lot ol gueiies; with
explicit loauing it is veiy oLvious when anu wheie gueiies aie Leing iun.
Explicit loauing is achieveu using the DbContext.Entry methou. The Entry methou gives
you access to all the inloimation that the DbContext has aLout an entity. This goes
Leyonu the values that aie stoieu in the piopeities ol the actual entity anu incluues
things such as the state ol the entity anu the oiiginal values loi each piopeity when it
was ietiieveu liom the uataLase. You`ll see a lot moie aLout this inloimation in Chap-
teis + anu 5. In auuition to inloimation aLout the entity, the Entry methou also gives
you access to some opeiations you can peiloim on the entity, incluuing loauing uata
loi navigation piopeities.
Once we have the entiy loi a given entity we can use the Collection anu Reference
methous to uiill into the inloimation anu opeiations loi navigation piopeities. One ol
the opeiations availaLle is the Load methou, which will senu a gueiy to the uataLase to
loau the contents ol the navigation piopeity.
Let`s take anothei look at loauing the Lodgings availaLle at the Gianu Canyon. This
time let`s auu a TestExplicitLoading methou that uses the Entry methou to loau the
uata (Example 2-25).
Exanp|c 2-25. Loading rc|atcd data with cxp|icit |oad
private static void TestExplicitLoading()
{
using (var context = new BreakAwayContext())
{
var query = from d in context.Destinations
where d.Name == "Grand Canyon"
select d;
var canyon = query.Single();
context.Entry(canyon)
.Collection(d => d.Lodgings)
.Load();
Console.WriteLine("Grand Canyon Lodging:");
foreach (var lodging in canyon.Lodgings)
{
Console.WriteLine(lodging.Name);
}
}
}
The liist pait ol the coue shoulu Le lamiliaiit uses a LINQ gueiy to locate the Gianu
Canyon Destination. The coue then calls the Entry methou, passing in the canyon oL-
ject. Fiom theie the Collection methou is useu to uiill into the Lodgings navigation
piopeity. Collection anu Reference use a lamLua expiession to specily the piopeity to
uiill into. Theie aie also stiing-Laseu alteinatives to these methous, Lut the lamLua
Loading Related Data | 37
veision ensuies we get compile-time checking ol the paiametei. Finally, the Load
methou is useu to gueiy loi the ielateu uata anu Liing it into memoiy.
Il you upuate the Main methou to call TestExplicitLoading anu then iun the application,
you will see two gueiies iun against the uataLase (Figuie 2-3). The liist one iuns when
the coue ieguests the single iesult ol the gueiy loi the Gianu Canyon, Ly calling Sin
gle on gueiy. The seconu gueiy is asking loi all Lodging at the Gianu Canyon anu iuns
as a iesult ol the call to Load.
Iigurc 2-3. Exp|icit |oading runs scparatc qucrics jor rc|atcd data
You`ve seen that explicit loauing can Le useu to loau the entiie contents ol a collection
navigation piopeity Lut it can also Le useu to loau just some ol the contents, Laseu on
a LINQ gueiy. You`ll see this in Explicit Loauing a SuLset ol the Contents ol a Nav-
igation Piopeity on page +1.
Explicit loauing ol a ieleience navigation piopeity looks veiy similai, except you use
the Reference methou iathei than Collection. Foi example, il you wanteu to loau the
PrimaryContact ol some louging, you coulu wiite this:
var lodging = context.Lodgings.First();
context.Entry(lodging)
.Reference(l => l.PrimaryContact)
.Load();
Checking If a Navigation Property Has Been Loaded
The Reference anu Collection methous also give you access to the IsLoaded piopeity.
The IsLoaded methou will tell you whethei the entiie contents ol the navigation piop-
eity have Leen loaueu liom the uataLase oi not. The IsLoaded piopeity will Le set to
tiue when lazy, eagei, oi explicit loauing is useu to loau the contents ol the navigation
piopeity. Auu the TestIsLoaded methou shown in Example 2-26.
Exanp|c 2-2. Tcsting ij a navigation propcrty has bccn |oadcd with |sLoadcd
private static void TestIsLoaded()
{
using (var context = new BreakAwayContext())
{
var canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
select d).Single();
var entry = context.Entry(canyon);
38 | Chapter 2:Querying with DbContext
Console.WriteLine(
"Before Load: {0}",
entry.Collection(d => d.Lodgings).IsLoaded);
entry.Collection(d => d.Lodgings).Load();
Console.WriteLine(
"After Load: {0}",
entry.Collection(d => d.Lodgings).IsLoaded);
}
}
The coue uses a LINQ gueiy to loau the Gianu Canyon Destination liom the uataLase.
The value assigneu to the IsLoaded piopeity loi the Lodgings piopeity is then piinteu
out to the console. Explicit loauing is useu to loau the contents ol the Lodgings piopeity
anu the value ol IsLoaded is piinteu to the console again. Il you upuate the Main methou
to call TestIsLoaded anu then iun the application, you will see that the value ol IsLoa
ded is set to tiue altei the explicit loau is peiloimeu:
Before Load: False
After Load: True
Il you aie peiloiming an explicit loau, anu the contents ol the navigation piopeity may
have alieauy Leen loaueu, you can use the IsLoaded llag to ueteimine il the loau is
ieguiieu oi not.
Querying Contents of a Collection Navigation Property
So lai you`ve lookeu at loauing the entiie contents ol a collection navigation piopeity
so that you can woik with the uata in memoiy. Il you wanteu to liltei the contents ol
a navigation piopeity you coulu uo this altei you`u Liought eveiything into memoiy,
using LINQ to OLjects. Howevei, il you aie only inteiesteu in a suLset ol the contents,
it may make sense to just Liing the Lits you aie inteiesteu in into memoiy. Oi il you
just want a count, oi some othei calculation, it may make sense just to calculate the
iesult in the uataLase anu not Liing any ol the uata into memoiy.
Once you`ve useu Entry anu Collection to uiill into a collection navigation piopeity,
you can then use the Query methou to get a LINQ gueiy iepiesenting the contents ol
that piopeity. Because it`s a LINQ gueiy, you can then uo luithei lilteiing, soiting,
aggiegation, anu the like.
Assume you wanteu to linu all Lodgings at the Gianu Canyon that aie less than ten
miles liom the neaiest aiipoit. You coulu just use LINQ to gueiy the contents ol the
Lodgings piopeity ol the Gianu Canyon, something like Example 2-27.
Exanp|c 2-27. |n-ncnory qucry oj a navigation propcrty
private static void QueryLodgingDistance()
{
using (var context = new BreakAwayContext())
Querying Contents of a Collection Navigation Property | 39
{
var canyonQuery = from d in context.Destinations
where d.Name == "Grand Canyon"
select d;
var canyon = canyonQuery.Single();
var distanceQuery = from l in canyon.Lodgings
where l.MilesFromNearestAirport <= 10
select l;
foreach (var lodging in distanceQuery)
{
Console.WriteLine(lodging.Name);
}
}
}
The pioLlem with this coue is that distanceQuery is using LINQ to OLjects to gueiy
the contents ol the Lodgings navigation piopeity. This will cause the piopeity to Le lazy
loaueu, pulling the entiie contents into memoiy. The coue then immeuiately lilteis out
some ol the uata, meaning theie was no neeu to pull it into memoiy. Let`s iewiite the
QueryLodgingDistance methou liom Example 2-27 to use Query, as shown in Exam-
ple 2-2S.
Exanp|c 2-28. Databasc qucry oj a navigation propcrty
private static void QueryLodgingDistance()
{
using (var context = new BreakAwayContext())
{
var canyonQuery = from d in context.Destinations
where d.Name == "Grand Canyon"
select d;
var canyon = canyonQuery.Single();
var lodgingQuery = context.Entry(canyon)
.Collection(d => d.Lodgings)
.Query();
var distanceQuery = from l in lodgingQuery
where l.MilesFromNearestAirport <= 10
select l;
foreach (var lodging in distanceQuery)
{
Console.WriteLine(lodging.Name);
}
}
}
This upuateu coue uses the Query methou to cieate a LINQ to Entities gueiy loi the
Lodgings associateu with the Gianu Canyon. It then composes on that gueiy to ask loi
40 | Chapter 2:Querying with DbContext
just the Lodgings that aie within ten miles ol an aiipoit. Vhen iteiating ovei this gueiy,
Entity Fiamewoik takes caie ol the tianslation to SQL anu peiloims the liltei on Mile
sFromNearestAirport in the uataLase. This means that only the uata you caie aLout is
Liought Lack into memoiy.
Peihaps you want to know how many Lodgings aie availaLle at the Gianu Canyon. You
coulu loau all the Lodgings anu get a count, Lut why Liing all that uata into memoiy
just to get a single integei iesult? Auu a QueryLodgingCount methou that uses Query to
get the count without loauing the uata (Example 2-29).
Exanp|c 2-29. Using Qucry to gct a count oj Lodgings
private static void QueryLodgingCount()
{
using (var context = new BreakAwayContext())
{
var canyonQuery = from d in context.Destinations
where d.Name == "Grand Canyon"
select d;
var canyon = canyonQuery.Single();
var lodgingQuery = context.Entry(canyon)
.Collection(d => d.Lodgings)
.Query();
var lodgingCount = lodgingQuery.Count();
Console.WriteLine("Lodging at Grand Canyon: " + lodgingCount);
}
}
The coue loaus the Gianu Canyon uestination anu then uses Entry anu Collection to
uiill into the Lodgings navigation piopeity. Fiom theie it uses the Query methou to get
a gueiy iepiesenting the contents ol the navigation piopeity. It then uses the LINQ
Count methou to mateiialize just the count ol the iesults ol the gueiy. Because it is using
the LINQ to Entities pioviuei, it iecognizes that you want the count anu pushes the
entiie gueiy to the uataLase so that only the single integei iesult is ietuineu liom the
uataLase. Il you upuate the Main methou to call QueryLodgingCount anu iun the appli-
cation you will see the count coiiectly uisplayeu:
Lodging at Grand Canyon: 2
Explicit Loading a Subset of the Contents of a Navigation Property
You can comLine the Query anu Load methous to peiloim a ji|tcrcd cxp|icit |oad. That`s
an explicit loau that only loaus a suLset ol the contents ol a navigation piopeity. Foi
example, you may want to just loau the Lodgings at the Gianu Canyon that contain the
woiu Hotel in theii Name:
Querying Contents of a Collection Navigation Property | 41
context.Entry(canyon)
.Collection(d => d.Lodgings)
.Query()
.Where(l => l.Name.Contains("Hotel"))
.Load();
It`s impoitant to iememLei that calling Load will not cleai any oLjects that aie alieauy
in the navigation piopeity. So il you loaueu Lougings at the Gianu Canyon that contain
the woiu Hotel anu then also loaueu Lougings that contain the woiu Campsite,
the Lougings navigation piopeity will contain Loth hotels anu campsites.
42 | Chapter 2:Querying with DbContext
CHAPTER 3
Adding, Changing, and
Deleting Entities
In the pievious chaptei you saw how to get uata liom the uataLase into memoiy. But
this is only hall the stoiy. Most applications also neeu to make changes to that uata
anu then push those changes Lack into the uataLase. In this chaptei we will take a look
at how Entity Fiamewoik can Le useu to make changes to uata. These changes lall into
thiee main categoiies: auuing new uata, changing existing uata anu ueleting existing
uata.
Vhile looking at gueiying, we saw the main Lenelit ol using an OLject Relational
Mappei (ORM), like Entity Fiamewoik, is that application coue is wiitten in teims ol
youi oLject mouel. As you wiite youi application, you uon`t neeu to Le looking at the
shape ol youi taLles anu columns. Noi uo you neeu to know how to wiite INSERT,
UPDATE, anu DELETE statements loi youi uataLase. Entity Fiamewoik will take caie ol
tianslating the opeiations you peiloim on youi oLjects into SQL statements that will
push these changes into the uataLase.
As you peiloim opeiations on youi oLject instances, Entity Fiamewoik uses its changc
trac|cr to keep tiack ol what you have uone. Vhen you`ie ieauy to commit the changes
to the uataLase, you call the SaveChanges methou. SaveChanges will invoke the updatc
pipc|inc, which is iesponsiLle loi tianslating the changes to youi oLject instances into
SQL statements that aie executeu against youi uataLase. Il you`ve uevelopeu applica-
tions using Entity Fiamewoik`s ObjectContext, you shoulu Le lamiliai with this piocess.
Because Entity Fiamewoik is awaie ol the ielationships Letween youi entities, il you
aie saving ielateu oLjects, it will take caie ol oiueiing the SQL statements to ensuie
changes aie applieu in the coiiect oiuei. Foi example, you may Le ueleting an existing
Destination anu also moving the Lodgings associateu with that Destination to a uil-
leient Destination. Entity Fiamewoik will ueteimine that the Lodging iecoius must Le
upuateu Leloie the Destination iecoiu is ueleteu, iegaiuless ol the oiuei that you pei-
loimeu these opeiations in memoiy.
43
Entity Fiamewoik allows you to make changes that allect single oLjects, a ielationship
Letween two oLjects, oi an entiie giaph ol oLjects. In this chaptei we aie going to take
a look at changes allecting a single oLject anu ielationships Letween oLjects. In the next
chaptei we`ll take a look at some auvanceu scenaiios wheie opeiations can allect a
whole giaph ol oLjects.
Working with Single Entities
Theie aie thiee types ol changes that can allect a single entityauuing a new entity,
changing the piopeity values ol an existing entity, oi ueleting an existing entity. In this
section you`ll leain how to make each ol these changes using Entity Fiamewoik.
Adding New Entities
Auuing a new oLject with Entity Fiamewoik is as simple as constiucting a new instance
ol youi oLject anu iegisteiing it using the Add methou on DbSet. Let`s say you wanteu
to auu a Machu Picchu uestination to the uataLase. The AddMachuPicchu methou shown
in Example 3-1 uemonstiates this.
Exanp|c 3-1. Adding a ncw Dcstination
private static void AddMachuPicchu()
{
using (var context = new BreakAwayContext())
{
var machuPicchu = new Destination
{
Name = "Machu Picchu",
Country = "Peru"
};
context.Destinations.Add(machuPicchu);
context.SaveChanges();
}
}
The coue constiucts a new Destination loi Machu Picchu anu then calls Add on the
Destinations set you uelineu in the BreakAwayContext. Finally, the coue calls Save
Changes, which will take the changes anu save them to the uataLase. Ve see that a single
INSERT statement is executeu against oui uataLase:
exec sp_executesql N'
insert [baga].[Locations]
([LocationName], [Country], [Description], [Photo])
values (@0, @1, null, null)
select [LocationID]
from [baga].[Locations]
where @@ROWCOUNT > 0 and [LocationID] = scope_identity()',
44 | Chapter 3:Adding, Changing, and Deleting Entities
N'@0 nvarchar(max) ,@1 nvarchar(max) ',
@0=N'Machu Picchu',@1=N'Peru'
Notice that Entity Fiamewoik is using the mapping we supplieu as it tianslates the
oLject changes into SQL. Foi example, we mappeu the Destination class in oui uomain
mouel to the baga.Locations taLle in the uataLase. Entity Fiamewoik uses this inloi-
mation to constiuct an INSERT statement that taigets the Locations taLle. The key ol
Destination is an iuentity column, meaning the value is geneiateu Ly the uataLase when
the iecoiu is inseiteu. Because ol this, Entity Fiamewoik incluues some auuitional SQL
to letch this newly cieateu value altei the INSERT statement has executeu. Entity Fiame-
woik will then take the ietuineu value anu assign it to the DestinationId piopeity ol
the oLject that was auueu.
In this example we useu the uelault constiuctoi ol oui POCO class to
cieate the new instance to Le inseiteu. A little latei in this chaptei you`ll
leain aLout change tiacking pioxies anu how to cieate new instances ol
pioxies to inseit.
Changing Existing Entities
Changing existing oLjects is as simple as upuating the value assigneu to the piopeity(s)
you want changeu anu calling SaveChanges. Peihaps we want to change the Gianu
Canyon Destination anu assign a Description to it so that BAGA customeis know just
how gianu it is. Auu the ChangeGrandCanyon methou shown in Example 3-2.
Exanp|c 3-2. Changing an cxisting Dcstination
private static void ChangeGrandCanyon()
{
using (var context = new BreakAwayContext())
{
var canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
select d).Single();
canyon.Description = "227 mile long canyon.";
context.SaveChanges();
}
}
This coue uses a LINQ gueiy to loau the Gianu Canyon Destination into memoiy. It
then assigns the new value to the Description piopeity ol the loaueu Destination. Vith
the changes completeu it calls SaveChanges, which issues an UPDATE statement to the
uataLase:
exec sp_executesql N'
update [baga].[Locations]
set [Description] = @0
where ([LocationID] = @1)
Working with Single Entities | 45
',N'@0 nvarchar(500),@1 int',
@0=N'227 mile long canyon.',@1=1
Again we see Entity Fiamewoik using the mapping to constiuct the appiopiiate SQL
statement. This time it`s an UPDATE statement against the baga.Locations taLle. Entity
Fiamewoik uses the key ol the entity to iuentily the iecoiu to Le upuateu. In oui case,
that`s DestinationId, which is mappeu to the LocationId column. This iesults in a
WHERE clause that lilteis Laseu on the LocationId column with the value liom the Des
tinationId piopeity in the oLject Leing upuateu.
Deleting Existing Entities
To uelete an entity using Entity Fiamewoik, you use the Remove methou on DbSet.
Remove woiks loi Loth existing anu newly auueu entities. Calling Remove on an entity
that has Leen auueu Lut not yet saveu to the uataLase will cancel the auuition ol the
entity. The entity is iemoveu liom the change tiackei anu is no longei tiackeu Ly the
DbContext. Calling Remove on an existing entity that is Leing change-tiackeu will iegistei
the entity loi ueletion the next time SaveChanges is calleu.
You may Le wonueiing why the Entity Fiamewoik team chose to call
the methou Remove iathei than Delete, anu loi that mattei, why they
chose Add insteau ol Insert. The names weie chosen loi consistency with
othei collections anu sets in the .NET Fiamewoik. Othei collections all
use the Add/Remove paii ol methous to Liing elements into anu out ol the
collection.
Let`s auu a DeleteWineGlassBay methou that will uelete the Vine Glass Bay Destina
tion liom oui uataLase (Example 3-3).
Exanp|c 3-3. Dc|cting an cxisting Dcstination
private static void DeleteWineGlassBay()
{
using (var context = new BreakAwayContext())
{
var bay = (from d in context.Destinations
where d.Name == "Wine Glass Bay"
select d).Single();
context.Destinations.Remove(bay);
context.SaveChanges();
}
}
The coue uses a LINQ gueiy to loau the Vine Glass Bay Destination liom the uataLase.
It then calls Remove on the Destinations set you have uelineu on the BreakAwayCon
text. Now that Vine Glass Bay is iegisteieu loi ueletion (at least liom oui uataLase),
we call SaveChanges anu a DELETE statement is iun against oui uataLase:
46 | Chapter 3:Adding, Changing, and Deleting Entities
exec sp_executesql N'
delete [baga].[Locations]
where ([LocationID] = @0)',
N'@0 int',
@0=3
This is a veiy simple DELETE statement that uses the key value liom the Vine Glass Bay
oLject to Luilu a WHERE clause that iuentilies the oLject we aie ueleting.
DbSet.Remove Versus Remove on a Collection Navigation Property
Vhile calling Remove on a DbSet will maik an entity loi ueletion, calling Remove on a
collection navigation piopeity will not. Removing an entity liom a collection navigation
piopeity will maik the ielationship Letween the two entities as ueleteu Lut not the
entities themselves. Moie inloimation is pioviueu in Removing a Relationship Be-
tween OLjects on page 57.
Deleting without loading from the database
DbSet.Remove lollows the same iule that we`ve always hau to contenu with loi having
Entity Fiamewoik ueleting oLjects. The oLject must Le tiackeu Ly the change tiackei
anu maikeu as Deleted in oiuei loi SaveChanges to constiuct a DELETE commanu to senu
to the uataLase.
Il you know you neeu to uelete an entity, Lut it`s not alieauy in memoiy, it`s a little
inellicient to ietiieve that entity liom the uataLase just to uelete it. Il you know the key
ol the entity you want to uelete, you can attach a stub that iepiesents the entity to Le
ueleteu, anu then uelete this stuL. A stuL is an instance ol an entity that just has the
key value assigneu. The key value is all that`s ieguiieu loi ueleting entities.
Vhen attaching a stuL you use the DbSet.Attach methou to let Entity Fiamewoik know
that it`s an existing entity. Once an entity is attacheu, it Lehaves just like an entity that
was ietiieveu liom the uataLase. So calling DbSet.Remove will cause a DELETE statement
to Le sent to the uataLase uuiing SaveChanges. Foi example, the lollowing coue woulu
uelete the Destination with an ID ol 2, without loauing it liom the uataLase:
var toDelete = new Destination { DestinationId = 2 };
context.Destinations.Attach(toDelete);
context.Destinations.Remove(toDelete);
context.SaveChanges();
Il an entity has Leen loaueu into memoiy, you won`t Le aLle to attach a stuL loi the
entity. Doing so woulu cause two existing entities with the same key value to Le tiackeu
Ly the context. Il you tiy anu uo this you will get an InvalidOperationException stating
An oLject with the same key alieauy exists in the OLjectStateManagei.
Anothei way to uelete entities without loauing them is to use DbContext.Database.Exe
cuteSqlCommand to execute some iaw SQL to peiloim the ueletion in the uataLase. Foi
Working with Single Entities | 47
example, the lollowing coue woulu uelete the Hawaii Destination without loauing it
liom the uataLase:
context.Database.ExecuteSqlCommand(
"DELETE FROM baga.Locations WHERE LocationName = 'Hawaii'");
Because we aie using iaw SQL, we aie Lypassing any mapping that is uone using Entity
Fiamewoik. In the aLove coue we neeueu to iememLei that the Destination class is
mappeu to the baga.Locations taLle anu that the Name piopeity is mappeu to the
LocationName column.
Deleting an object with related data
Il you aie ueleting oLjects that have ielateu uata, you may neeu to upuate ielateu uata
loi the uelete to succeeu. The ieguiieu upuates to ielateu uata will uepenu on whethei
the ielationship is optional oi ieguiieu. Optional ielationships mean that the chilu
entity can exist in the uataLase without a paient assigneu. Foi example, a Reserva
tion can exist in the uataLase without a Trip assigneu. Reguiieu ielationships mean
the chilu entity cannot exist without a paient assigneu. Foi example, a Lodging cannot
exist in the uataLase without Leing assigneu to a Destination.
Il you uelete an entity that is the paient ol an optional ielationship, the ielationship
Letween the paient anu any chilu entities can Le ueleteu, too. This means the chilu
entities will Le upuateu so that they aie no longei assigneu to a paientthe loieign key
column in the uataLase will Le set to null. Entity Fiamewoik will automatically uelete
the ielationship loi you il the chilu entity has Leen loaueu into memoiy liom the
uataLase.
Let`s stait Ly seeing what happens il we uelete a paient entity ol an optional ielationship
when the chilu entity isn`t loaueu into memoiy. Theie is an optional ielationship Le-
tween a Reservation anu a Trip: Trip is the paient anu Reservation is the chilu. Auu a
methou that tiies to uelete a Trip without its chilu Reservation loaueu into memoiy
(Example 3-+).
Exanp|c 3-1. Dc|cting a Trip without its chi|d Rcscrvation |oadcd
private static void DeleteTrip()
{
using (var context = new BreakAwayContext())
{
var trip = (from t in context.Trips
where t.Description == "Trip from the database"
select t).Single();
context.Trips.Remove(trip);
context.SaveChanges();
}
}
48 | Chapter 3:Adding, Changing, and Deleting Entities
Il you upuate the Main methou to call DeleteTrip anu iun the application, you will get
a DbUpdateException inloiming you that theie was an eiioi while saving. Il you uiill
into the InnerException piopeities, you will see that the inneimost exception is a SqlEx
ception stating that The DELETE statement conllicteu with the REFERENCE con-
stiaint `FKReseivationsTiipsTiipIuentiliei`. You get this exception Lecause theie
is still a Reservation in the uataLase that has a loieign key pointing to the Trip you aie
tiying to uelete. Let`s upuate the DeleteTrip methou so that the Reservation that iel-
eiences the Trip we aie ueleting is loaueu into memoiy (Example 3-5).
Exanp|c 3-5. Dc|cting a Trip with its chi|d Rcscrvation |oadcd
private static void DeleteTrip()
{
using (var context = new BreakAwayContext())
{
var trip = (from t in context.Trips
where t.Description == "Trip from the database"
select t).Single();
var res = (from r in context.Reservations
where r.Trip.Description == "Trip from the database"
select r).Single();
context.Trips.Remove(trip);
context.SaveChanges();
}
}
The upuateu coue uses a seconu LINQ gueiy to loau the single Reservation that is
assigneu to the Trip that is Leing ueleteu. Il you iun the application again, it will succeeu
anu two SQL statements aie sent to the uataLase (Figuie 3-1).
Iigurc 3-1. Dc|cting parcnt cntity and optiona| rc|ationship to chi|d cntity
The upuate statement sets the loieign key column ol the Reservation to null, so that it
is no longei ielateu to the Trip that is Leing ueleteu. The uelete statement then ueletes
the Trip liom the uataLase.
Reguiieu ielationships aie a Lit uilleient Lecause the loieign key column in the uataLase
can`t Le set to null. Il you uelete a paient entity, each chilu must eithei Le ueleteu oi
upuateu to Lelong to a uilleient paient entity. You can eithei uo this manually oi have
the chilu entities automatically ueleteu, using a cascaue uelete. Failuie to uelete oi
ieassign chilu iecoius when you uelete a paient will iesult in a ieleiential integiity
constiaint violation when you attempt to SaveChanges.
In oui mouel theie is a cascaue uelete uelineu Letween Lodging anu Destination. Il we
uelete a Destination, the Lodging instances that aie assigneu to it will get automatically
Working with Single Entities | 49
ueleteu. In oui uataLase theie is a Gianu Canyon Destination, which has two ielateu
Lodgings. Auu the DeleteGrandCanyon methou shown in Example 3-6.
Exanp|c 3-. Dc|cting thc Grand Canyon with rc|atcd Lodgings
private static void DeleteGrandCanyon()
{
using (var context = new BreakAwayContext())
{
var canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
select d).Single();
context.Entry(canyon)
.Collection(d => d.Lodgings)
.Load();
context.Destinations.Remove(canyon);
context.SaveChanges();
}
}
The coue loaus the Gianu Canyon Destination liom the uataLase anu then uses eagei
loauing to ensuie the ielateu Lodgings aie also loaueu into memoiy. The coue then
maiks the Gianu Canyon loi ueletion anu pushes the changes to the uataLase. Il you
upuate the Main methou to call DeleteGrandCanyon anu iun the application, thiee SQL
commanus get sent to the uataLase (Figuie 3-2).
Iigurc 3-2. Dc|cting thc parcnt oj a rcquircd rc|ationship with cascadc dc|ctc
Because the ielateu Lodgings weie loaueu into memoiy, anu we hau a cascaue uelete
iule conliguieu, Entity Fiamewoik has automatically ueleteu the ielateu Lodgings. The
liist two delete statements aie ueleting the ielateu Lodgings anu the linal delete state-
ment ueletes the Gianu Canyon Destination.
Cascaue uelete is also conliguieu in the uataLase, so we uon`t neeu to loau the ielateu
uata loi it to Le automatically ueleteu. Mouily the DeleteGrandCanyon methou so that
it no longei loaus the ielateu Lodgings into memoiy (Example 3-7).
Exanp|c 3-7. Dc|cting Grand Canyon without Lodgings |oadcd
private static void DeleteGrandCanyon()
{
using (var context = new BreakAwayContext())
{
var canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
50 | Chapter 3:Adding, Changing, and Deleting Entities
select d).Single();
context.Destinations.Remove(canyon);
context.SaveChanges();
}
}
Il you iun the application again, a single delete commanu is sent to the uataLase, to
uelete the Gianu Canyon Destination. Because theie is a cascaue uelete conliguieu on
the loieign key constiaint Letween Lodging anu Destination, the uataLase has taken
caie ol ueleting the ielateu uata. Cascaue uelete is coveieu in uetail in Progranning
Entity Irancwor|, 2c.
Il we uiun`t have a cascaue uelete uelineu, we woulu neeu to manually maik each ol
the ielateu Lodging entities as ueleteu Leloie attempting to save. To maik each ol the
ielateu Lodgings as ueleteu, you woulu neeu to iteiate thiough the Lodgings piopeity
anu call DbSet.Remove on each Lodging. Because Entity Fiamewoik is going to upuate
the Lodgings piopeity to iemove each Lodging as it is maikeu loi ueletion, you neeu to
use ToList to cieate a copy ol the Lodgings. Failuie to cieate a copy will iesult in the
contents ol Lodgings changing as it is iteiateu, which is not suppoiteu Ly the .NET
Fiamewoik:
foreach (var lodging in canyon.Lodgings.ToList())
{
context.Lodgings.Remove(lodging);
}
An alteinative to ueleting the chilu entities is to assign them to a new paient. Foi ex-
ample, we coulu move the Lodgings liom Gianu Canyon to Hawaii Leloie ueleting the
Gianu Canyon:
foreach (var lodging in canyon.Lodgings.ToList())
{
lodging.Destination = hawaii;
}
You`ll leain moie aLout changing ielationships in Changing a Relationship Between
OLjects on page 56.
Multiple Changes at Once
So lai we have lookeu at making a single change lolloweu Ly a call to SaveChanges to
push that change to the uataLase. Youi application may want to inteispeise many
gueiies anu changes anu then push all the changes to the uataLase at once. Let`s auu a
MakeMultipleChanges methou that uoes just this (Example 3-S).
Exanp|c 3-8. Mu|tip|c changcs in onc transaction
private static void MakeMultipleChanges()
{
using (var context = new BreakAwayContext())
Working with Single Entities | 51
{
var niagaraFalls = new Destination
{
Name = "Niagara Falls",
Country = "USA"
};
context.Destinations.Add(niagaraFalls);
var wineGlassBay = (from d in context.Destinations
where d.Name == "Wine Glass Bay"
select d).Single();
wineGlassBay.Description = "Picturesque bay with beaches.";
context.SaveChanges();
}
}
The coue cieates a new Destination loi Niagaia Falls anu auus it to the Destinations
set. It then ietiieves the Vine Glass Bay Destination anu changes its uesciiption. Once
these changes aie maue, SaveChanges is calleu to push the changes to the uataLase. Il
you upuate the Main methou to call MakeMultipleChanges anu iun the application you
will see thiee statements aie iun against the uataLase (Figuie 3-3).
Iigurc 3-3. Mu|tip|c changcs jron onc ca|| to SavcChangcs
The liist is a SELECT statement to letch the Vine Glass Bay Destination. The next two
statements aie an UPDATE anu an INSERT that aie iun when we call SaveChanges.
SaveChanges is tiansactional, meaning that it eithei pushes all the
changes to the uataLase oi none ol them. Il one change lails, any changes
that have alieauy Leen maue aie iolleu Lack anu the uataLase is lelt in
the state it was in Leloie SaveChanges was calleu. You can leain moie
aLout how Entity Fiamewoik uses tiansactions Ly uelault anu how to
oveiiiue that uelault Lehavioi in Chaptei 20 ol Progranning Entity
Irancwor|, 2c.
The Find or Add Pattern
You may have noticeu that DbSet.Add ietuins an oLject. It ietuins the same oLject that
you pass into the methou. This may seem a little stiange at liist, Lut it enaLles a nice
couing pattein that you may linu convenient to use in youi applications. Youi
application might allow a usei to seaich loi a Person Laseu on his oi
52 | Chapter 3:Adding, Changing, and Deleting Entities
hei SocialSecurityNumber. Il the Person is lounu, youi coue can use the existing entity.
But il the Person isn`t locateu, you want to cieate a new Person with the supplieu
SocialSecurityNumber. The FindOrAddPerson methou shown in Example 3-9 uemon-
stiates this pattein.
Exanp|c 3-9. Adding a ncw Pcrson ij cxisting rccord docsn`t cxist
private static void FindOrAddPerson()
{
using (var context = new BreakAwayContext())
{
var ssn = 123456789;
var person = context.People.Find(ssn)
?? context.People.Add(new Person
{
SocialSecurityNumber = ssn,
FirstName = "<enter first name>",
LastName = "<enter last name>"
});
Console.WriteLine(person.FirstName);
}
}
RememLei that DbSet.Find is an easy way to locate entities Laseu on theii key values.
Il it linus the entity eithei in memoiy oi in the uataLase, it will ietuin the coiiect in-
stance. But il it can`t locate the entity, Find will ietuin null. You can comLine Find with
the ?? opeiatoi that allows you to pioviue an alteinate value to ietuin il some coue
ietuins null. In the example aLove, we attempt to locate the entity Ly using Find Laseu
on its key. Il Find uoesn`t locate the entity, we auu a new Person to the context insteau.
The ?? opeiatoi is specilic to C=; howevei, the same logic can Le wiitten in VB.NET
using the If methou:
Dim person = If(context.People.Find(ssn), context.People.Add(
New Person() With {
.SocialSecurityNumber = ssn,
.FirstName = "<enter first name>",
.LastName = "<enter last name>"
}
))
Working with Relationships
Now that you know how to auu, change, anu uelete entities, it`s time to look at how
we change ielationships Letween those entities. Youi uomain mouel exposes ielation-
ships using navigation piopeities anu, optionally, a loieign key piopeity. Changing a
ielationship is achieveu Ly changing the values assigneu to those piopeities.
Working with Relationships | 53
Given that a ielationship can Le iepiesenteu Ly up to thiee piopeities (two navigation
piopeities anu a loieign key piopeity), you may Le wonueiing il you neeu to upuate
all thiee just to change the ielationship. Upuating just one ol these piopeities is enough
to let Entity Fiamewoik know aLout the change. It is also line to upuate moie than one
ol the piopeities il you want to, pioviueu that the changes iepiesent the same change.
Vhen you call SaveChanges, Entity Fiamewoik will take caie ol upuating the iest ol
these piopeities loi you; this is known as rc|ationship jix-up. Rathei than waiting loi
SaveChanges to lix up the piopeities, you can tiiggei this lix-up on uemanu Ly calling
DetectChanges oi have it happen in ieal-time Ly using change tiacking pioxies. Both ol
these concepts aie uesciiLeu latei in this chaptei.
Vhile the Lasics ol changing ielationships aie guite simple, theie aie a lot ol intiicate
uetails to Le lamiliai with as you get into moie auvanceu ielationship scenaiios. These
intiicacies aie not specilic to the DLContext API anu aie well Leyonu the scope ol this
Look.
You can linu a uetaileu look at ielationships in Chaptei 19 ol Piogiam-
ming Entity Fiamewoik, 2e.
Adding a Relationship Between Objects
To auu a new ielationship, you neeu to assign one ol the oLjects in the ielationship to
the navigation piopeity ol the othei oLject. Il the navigation piopeity you want to
change is a ieleience (loi example, the Destination piopeity ol the Resort class), you
set the value to the ielateu oLject. Il the navigation piopeity is a collection (loi example,
the Payments piopeity ol the Reservation class), you use the Add methou to auu it to
that collection. RememLei that the change can Le maue at one oi Loth enus ol the
ielationship.
Let`s assume you want to auu a Lodging iecoiu loi a new luxuiy iesoit that is opening,
anu you want to associate it with the Gianu Canyon. To lollow along, auu the New
GrandCanyonResort methou shown in Example 3-10.
Exanp|c 3-10. Adding a ncw rc|ationship
private static void NewGrandCanyonResort()
{
using (var context = new BreakAwayContext())
{
var resort = new Resort
{
Name = "Pete's Luxury Resort"
};
context.Lodgings.Add(resort);
54 | Chapter 3:Adding, Changing, and Deleting Entities
var canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
select d).Single();
canyon.Lodgings.Add(resort);
context.SaveChanges();
}
}
This coue cieates the new Resort anu auus it to the Lodgings set we uelineu on BreakA
wayContext (iememLei that Resort ueiives liom Lodging). Next, the coue locates the
Gianu Canyon Destination that we want to auu this new Resort to. Then the new
Resort is auueu to the Lodgings collection ol the Gianu Canyon. This lets Entity Fiame-
woik know that the two oLjects aie ielateu. SaveChanges is then useu to push these
changes to the uataLase.
In Example 3-10, we auueu the new Resort to the Lodgings set anu then
auueu it to the canyon.Lodgings collection. The example is intentionally
ieuunuant loi claiity as you leain aLout the Lehaviois. The liist call
ensuies that the context knows that resort is new anu neeus to Le in-
seiteu into the uataLase. The seconu call then specilies that resort must
also Le ielateu to canyon. Vhile this makes it oLvious that resort is a
new entity, auuing it twice is not stiictly necessaiy in this paiticulai
example. Il you hau skippeu auuing the new resort to the context anu
only auueu it to canyon, Entity Fiamewoik woulu have lounu resort
Lecause it is now ieleienceu liom the navigation piopeity ol an entity
that is tiackeu Ly the context (canyon). Entity Fiamewoik woulu have
iecognizeu that resort was not Leing tiackeu anu in iesponse woulu
have assumeu the resort neeueu to Le in the Added state. Theieloie we
coulu have lelt out the line ol coue that auueu resort to context.Lodg
ings anu achieveu the same iesult.
In the coue we upuateu the collection enu ol a one-to-many ielationship. But iememLei
we can upuate eithei enu ol the ielationship. Rathei than auuing the new Resort to the
Destination.Lodgings collection, we coulu have set the Lodging.Destination piopeity
to the uesiieu Destination instance:
resort.Destination = canyon;
Lodging also exposes a loieign key piopeity, DestinationId, to iepiesent the ielation-
ship. Ve coulu also have upuateu that insteau:
resort.DestinationId = canyon.DestinationId;
Il you aie auuing an oLject to a collection navigation piopeity, you neeu to make suie
that the collection piopeity will Le initializeu. RememLei that, Ly uelault, piopeities
will Le assigneu a value ol null. In oui case we enaLleu lazy loauing on the Lodgings
piopeity, so Entity Fiamewoik took caie ol cieating a collection anu assigning it to the
Working with Relationships | 55
Lodgings piopeity. Il you aien`t using lazy loauing, you will neeu to incluue logic in
eithei youi classes oi the consuming coue to initialize the collection.
Changing a Relationship Between Objects
Changing a ielationship is actually the same as auuing a new ielationship. Vhen we
auu a ielationship, we aie changing it liom unassigneu to point to an entity. Changing
a ielationship to point liom one entity to anothei uses exactly the same piocess. To
change a ielationship, we locate the entity to Le changeu anu upuate the navigation
piopeity, oi loieign key piopeity.
Peihaps we maue a mistake while enteiing some uata anu the Gianu Hotel actually
exists at the Gieat Baiiiei Reel iathei than the Gianu Canyon. The ChangeLodgingDes
tination methou shown in Example 3-11 uemonstiates assigning a new ielationship
that will ieplace an existing ielationship.
Exanp|c 3-11. Updating an cxisting rc|ationship
private static void ChangeLodgingDestination()
{
using (var context = new BreakAwayContext())
{
var hotel = (from l in context.Lodgings
where l.Name == "Grand Hotel"
select l).Single();
var reef = (from d in context.Destinations
where d.Name == "Great Barrier Reef"
select d).Single();
hotel.Destination = reef;
context.SaveChanges();
}
}
The coue locates Loth the Gianu Hotel anu the Gieat Baiiiei Reel Ly using LINQ
gueiies. Next it upuates the ielationship Ly changing the Destination piopeity ol the
Gianu Hotel to point to the Gieat Baiiiei Reel. Theie is no neeu to iemove the existing
ielationship Letween the Gianu Hotel anu the Gianu Canyon. Entity Fiamewoik
knows that we want the ielationship to Le upuateu, which implies it will no longei
point to the olu value. Running the coue will iesult in a single update statement Leing
sent to the uataLase:
exec sp_executesql N'update [dbo].[Lodgings]
set [destination_id] = @0
where ([LodgingId] = @1)
',N'@0 int,@1 int',@0=4,@1=1
The upuate statement looks veiy similai to the one you saw eailiei in this chaptei, when
we mouilieu a String piopeity ol an entity. Entity Fiamewoik uses the Locations key
56 | Chapter 3:Adding, Changing, and Deleting Entities
value to locate the iecoiu to Le upuateu. This time, insteau ol upuating a simple column,
it is upuating the loieign key column to point to the piimaiy key ol the Destination we
upuateu oui Location to.
By now you`ve pioLaLly woikeu out that we coulu also make the change Ly auuing
hotel to the Lodgings piopeity ol reef:
reef.Lodgings.Add(hotel);
Ve coulu also make the change Ly setting the loieign key piopeity:
hotel.DestinationId = reef.DestinationId;
Removing a Relationship Between Objects
Let`s say that Dave is no longei the piimaiy contact loi Dave`s Dump. In lact, the seivice
is so Lau at this louging that they no longei have a contact at all. This means we simply
want to iemove the ielationship iathei than changing it to a new Person.
To iemove a ielationship, you can iemove the taiget oLject liom a collection navigation
piopeity. Alteinatively, you can set a ieleience navigation piopeity to null. Il youi
classes expose a nullaLle loieign key piopeity loi the ielationship, a thiiu option is to
set the loieign key to null.
Removing ielationships Ly changing the loieign key is only possiLle with
nullaLle piopeities (loi example, the PrimaryContactId piopeity ol the
Lodging class). Il youi loieign key is an int (loi example, the Destina
tionId piopeity ol the Lodging class), you won`t Le aLle to set the value
to null anu Ly convention, that ielationship woulu Le ieguiieu. Setting
the value to 0 woulu cause a piimaiy key/loieign key constiaint eiioi in
the uataLase, as theie will Le no paient whose piimaiy key is egual to 0.
Auu the RemovePrimaryContact methou shown in Example 3-12.
Exanp|c 3-12. Rcnoving a prinary contact jor Davc`s Dunp
private static void RemovePrimaryContact()
{
using (var context = new BreakAwayContext())
{
var davesDump = (from l in context.Lodgings
where l.Name == "Dave's Dump"
select l).Single();
context.Entry(davesDump)
.Reference(l => l.PrimaryContact)
.Load();
davesDump.PrimaryContact = null;
context.SaveChanges();
Working with Relationships | 57
}
}
The coue staits Ly letching Dave`s Dump liom the uataLase with a LINQ gueiy. Next
it loaus the ielateu PrimaryContact. Ve neeu to uo this so that something is actually
changing when we set the value to null. Because lazy loauing isn`t enaLleu loi this
piopeity it is alieauy null Ly uelault so the coue explicitly loaus the PrimaryContact.
Eagei loauing oi enaLling lazy loauing woulu achieve exactly the same thing. Once the
contact is loaueu, the PrimaryContact piopeity is set to null to let Entity Fiamewoik
know that we want to uelete this ielationship. Dave will not Le ueleteu liom the uata-
Lase, noi will Dave`s Dump, Lut they will no longei Le ielateu to each othei. Save
Changes will upuate the uataLase to null out the loieign key loi the piimaiy contact ol
Dave`s Dump.
This highlights one ol the auvantages ol exposing loieign key piopeities in youi classes.
Because we expose a loieign key piopeity loi the PiimaiyContact ielationship, anu that
piopeity useu a nullaLle integei (Nullable<int> oi int?), then we can set that piopeity
to null without the neeu to loau the ielateu uata. This woiks Lecause loieign key piop-
eities aie always populateu when you gueiy loi an entity, wheieas navigation piopeities
aie only populateu when you loau the ielateu uata. Let`s iewiite the RemovePrimaryCon
tact methou to mouily the loieign key piopeity iathei than the navigation piopeity
(Example 3-13).
Exanp|c 3-13. Rcnoving a prinary contact jor Davc`s Dunp using thc jorcign |cy
private static void RemovePrimaryContact()
{
using (var context = new BreakAwayContext())
{
var davesDump = (from l in context.Lodgings
where l.Name == "Dave's Dump"
select l).Single();
davesDump.PrimaryContactId = null;
context.SaveChanges();
}
}
In this example, we weie iemoving an optional ielationship; accoiuing to the mouel
we Luilt, it is line loi Lodging to exist without a PrimaryContact. Il we tiieu to iemove
a ieguiieu ielationship the outcome woulu Le a little uilleient. Let`s say we hau tiieu
to iemove the ielationship Letween Dave`s Dump anu the Gianu Canyon. Accoiuing
to the BAGA mouel, Lodging can`t exist without a Destination. Entity Fiamewoik
woulu allow us to set the Lodging.Destination piopeity to null, Lut it woulu thiow an
exception when we tiieu to SaveChanges. Ve aie alloweu to set the piopeity to null
pioviueu we set it to anothei valiu Destination Leloie we tiy anu SaveChanges.
58 | Chapter 3:Adding, Changing, and Deleting Entities
Given that Dave is no longei a contact, we may want to iemove him liom the uataLase,
iathei than just iemoving his ielationship as a piimaiy contact loi Dave`s Dump. Il you
uelete an entity theie is no neeu to uelete all the ielationships that they paiticipate in:
Entity Fiamewoik will automatically uelete them loi you. As uiscusseu in Deleting
Existing Entities on page +6, il any ol the ielationships aie ieguiieu you will neeu to
uelete oi ieassign any chilu entities Leloie saving.
Working with Change Tracking
Thioughout this chaptei you have seen that Entity Fiamewoik keeps tiack ol the
changes you make to youi oLjects. Entity Fiamewoik uses its changc trac|cr to uo this.
You can access the change tiackei inloimation, anu some change tiackingielateu op-
eiations, thiough the DbContext.ChangeTracker piopeity. You`ll see moie aLout the
Change Tiackei API in Chaptei 5.
Theie aie two uilleient ways that Entity Fiamewoik can tiack the changes to youi
oLjects: snapshot changc trac|ing oi changc trac|ing proxics.
Snapshot changc trac|ing
The coue wiitten so lai in this chaptei has ielieu on snapshot change tiacking. The
classes in oui mouel aie all POCO anu they uon`t contain any logic to notily Entity
Fiamewoik when a piopeity value is changeu. Because theie is no way to Le no-
tilieu when a piopeity value changes, Entity Fiamewoik will take a snapshot ol the
values in each piopeity when it liist sees an oLject anu stoie the values in memoiy.
This snapshot occuis when the oLject is ietuineu liom a gueiy oi when we auu it
to a DbSet. Vhen Entity Fiamewoik neeus to know what changes have Leen maue,
it will scan each oLject anu compaie its cuiient values to the snapshot. This piocess
ol scanning each oLject is tiiggeieu thiough a methou ol ChangeTracker calleu
DetectChanges.
Changc trac|ing proxics
The othei mechanism loi tiacking changes is thiough change tiacking pioxies,
which allow Entity Fiamewoik to Le notilieu ol changes as they aie maue. In
Chaptei 2, you leaineu aLout uynamic pioxies that aie cieateu loi lazy loauing.
Change tiacking pioxies aie cieateu using the same mechanism, Lut in auuition to
pioviuing loi lazy loauing, they also have the aLility to communicate changes to
the context.
To use change tiacking pioxies, you neeu to stiuctuie youi classes in such a way
that Entity Fiamewoik can cieate a uynamic type at iuntime that ueiives liom oui
POCO class anu oveiiiue eveiy piopeity. This uynamic type, known as a uynamic
pioxy, incluues logic in the oveiiiuuen piopeities to notily Entity Fiamewoik when
those piopeities aie changeu. In lact, all ol the iules loi cieating uynamic change
tiacking pioxies liom POCOs that you leaineu aLout il you ieau Piogiamming
Entity Fiamewoik, 2e, aie the same when you aie using POCOs with DbContext.
Working with Change Tracking | 59
Using Snapshot Change Tracking
Snapshot change tiacking uepenus on Entity Fiamewoik Leing aLle to uetect when
changes occui. The uelault Lehavioi ol the DLContext API is to automatically peiloim
this uetection as the iesult ol many events on the DbContext. DetectChanges not only
upuates the context`s state management inloimation so that changes can Le peisisteu
to the uataLase, it also peiloims ielationship lix-up when you have a comLination ol
ieleience navigation piopeities, collection navigation piopeities anu loieign keys. It`s
impoitant to have a cleai unueistanuing ol how anu when changes aie uetecteu, what
to expect liom it anu how to contiol it. This section auuiesses those conceins.
Understanding When Automatic Change Detection Occurs
The DetectChanges methou ol ObjectContext has Leen availaLle since Entity Fiamewoik
+ as pait ol the snapshot change tiacking pattein on POCO oLjects. Vhat`s uilleient
aLout DbContext.ChangeTracker.DetectChanges (which in tuin, calls ObjectCon
text.DetectChanges) is that theie aie many moie events that tiiggei an automatic call
to DetectChanges. Heie is the list ol the methou calls you shoulu alieauy Le lamiliai
with that will cause DetectChanges to uo its joL:
DbSet.Add
DbSet.Find
DbSet.Remove
DbSet.Local
DbContext.SaveChanges
Running any LINQ gueiy against a DLSet
Theie aie moie methous that will tiiggei DetectChanges. You`ll leain moie aLout these
methous thioughout the iest ol this Look:
DbSet.Attach
DbContext.GetValidationErrors
DbContext.Entry
DbChangeTracker.Entries
Controlling When DetectChanges Is Called
The most oLvious time that Entity Fiamewoik neeus to know aLout changes is uuiing
SaveChanges, Lut theie aie also many otheis. Foi example, il we ask the change tiackei
loi the cuiient state ol an oLject, it will neeu to scan anu check il anything has changeu.
Scanning isn`t just iestiicteu to the oLject in guestion eithei. Consiuei a situation wheie
you gueiy loi a Lodging liom the uataLase anu then auu it to the Lodgings collection ol
a new Destination. This Lodging is now mouilieu Lecause assigning it to a new
60 | Chapter 3:Adding, Changing, and Deleting Entities
Destination changes its DestinationId piopeity. But to know that this change has oc-
cuiieu (oi hasn`t occuiieu) Entity Fiamewoik neeus to scan all ol the Destination
oLjects as well. Many ol the opeiations you peiloim on the DbContext API will cause
DetectChanges to Le iun.
In most cases DetectChanges is last enough that it uoesn`t cause peiloimance issues.
Howevei, il you have a veiy laige numLei ol oLjects in memoiy oi you aie peiloiming
a lot ol opeiations on DbContext in guick succession, the automatic DetectChanges Le-
havioi may Le a peiloimance concein. Foitunately you have the option to switch oll
the automatic DetectChanges Lehavioi anu call it manually when you know that it neeus
to Le calleu.
Entity Fiamewoik is Luilt on the assumption that you will call DetectChanges Leloie
eveiy API call il you have changeu any ol the entities since the last API call. This incluues
calling DetectChanges Leloie iunning any gueiies. Failuie to uo this can iesult in un-
expecteu siue ellects. DbContext takes caie ol this ieguiiement loi you pioviueu that
you leave automatic DetectChanges enaLleu. Il you switch it oll, you aie iesponsiLle loi
calling DetectChanges.
Voiking out when DetectChanges neeus to Le calleu isn`t as tiivial as it
may appeai. The Entity Fiamewoik team stiongly iecommenus that you
only swap to manually calling DetectChanges il you aie expeiiencing
peiloimance issues. It`s also iecommenueu to only opt out ol automatic
DetectChanges loi pooily peiloiming sections ol coue anu to ieenaLle it
once the section in guestion has linisheu executing.
Automatic DetectChanges can Le toggleu on anu oll via the DbContext.Configura
tion.AutoDetectChangesEnabled Boolean llag. Let`s auu a ManualDetectChanges methou
that uisaLles automatic DetectChanges anu oLseives the ellect this has (Example 3-1+).
Exanp|c 3-11. Manua||y ca||ing DctcctChangcs
private static void ManualDetectChanges()
{
using (var context = new BreakAwayContext())
{
context.Configuration.AutoDetectChangesEnabled = false;
var reef = (from d in context.Destinations
where d.Name == "Great Barrier Reef"
select d).Single();
reef.Description = "The world's largest reef.";
Console.WriteLine(
"Before DetectChanges: {0}",
context.Entry(reef).State);
context.ChangeTracker.DetectChanges();
Using Snapshot Change Tracking | 61
Console.WriteLine(
"After DetectChanges: {0}",
context.Entry(reef).State);
}
}
The coue switches oll automatic DetectChanges anu then gueiies loi the Gieat Baiiiei
Reel anu changes its uesciiption. The next line will wiite out the cuiient state that the
context thinks the reef entity is in. Ve then manually call DetectChanges anu iepeat
the piocess ol wiiting out the cuiient state. Accessing the cuiient state makes use ol
the Entry methou liom Change Tiackei API, which is uiscusseu in Chaptei 5. Il you
upuate the Main methou to call ManualDetectChanges, you will see the lollowing output:
Before DetectChanges: Unchanged
After DetectChanges: Modified
As expecteu, the context uoesn`t uetect that the reef entity is mouilieu until altei we
manually call DetectChanges. The ieason we get an incoiiect iesult is that we Lioke the
iule ol calling DetectChanges Leloie calling an API altei we hau mouilieu an entity.
Because we weie simply ieauing the state ol an entity, this uiun`t have any nasty siue
ellects.
The coue we saw in Example 3-9 uiun`t ieally Luy us anything Ly switching oll auto-
matic DetectChanges. Calling the DbContext.Entry methou woulu also have automati-
cally tiiggeieu DetectChanges il the change tiacking wasn`t uisaLleu.
Il you wiite tests to check the state ol entities anu you use this Entry
methou to inspect state, keep in minu that the Entry methou itsell calls
DetectChanges. This coulu inauveitently altei youi test iesults. You can
use the AutoDetectChangesEnabled conliguiation to have tightei contiol
ovei DetectChanges in this scenaiio.
Howevei il we weie peiloiming a seiies ol API calls on DbContext without changing any
oLjects in Letween, we coulu avoiu some unnecessaiy execution ol the DetectChanges
piocess. The AddMultipleDestinations methou shown in Example 3-15 uemonstiates
this.
Exanp|c 3-15. Adding nu|tip|c objccts without DctcctChangcs
private static void AddMultipleDestinations()
{
using (var context = new BreakAwayContext())
{
context.Configuration.AutoDetectChangesEnabled = false;
context.Destinations.Add(new Destination
{
Name = "Paris",
Country = "France"
});
62 | Chapter 3:Adding, Changing, and Deleting Entities
context.Destinations.Add(new Destination
{
Name = "Grindelwald",
Country = "Switzerland"
});
context.Destinations.Add(new Destination
{
Name = "Crete",
Country = "Greece"
});
context.SaveChanges();
}
}
This coue avoius loui unnecessaiy calls to DetectChanges that woulu have occuiieu
while calling the DbSet.Add anu SaveChanges methous. This example is useu puiely loi
uemonstiation puiposes anu is not a scenaiio wheie uisaLling DetectChanges is going
to pioviue any signilicant Lenelit. In Chaptei 5 you`ll leain aLout making changes to
youi oLjects using the Change Tiackei API. The Change Tiackei API enaLles you to
make changes to youi oLjects Ly going thiough a DLContext API. This appioach allows
you to change youi oLjects without the neeu to call DetectChanges.
Using DetectChanges to Trigger Relationship Fix-up
DetectChanges is also iesponsiLle loi peiloiming ielationship lix-up loi any ielation-
ships that it uetects have changeu. Il you have changeu some ielationships anu woulu
like to have all the navigation piopeities anu loieign key piopeities synchionizeu,
DetectChanges will achieve this. This can Le paiticulaily uselul in uata-Linuing scenaiios
wheie youi UI will change one ol the navigation piopeities (oi peihaps the loieign key
piopeity) Lut you then want the othei piopeities in the ielationship to Le upuateu to
iellect the change. The DetectRelationshipChanges methou in Example 3-16 uses
DetectChanges to peiloim ielationship lix-up.
Exanp|c 3-1. Using DctcctChangcs to jix up rc|ationships
private static void DetectRelationshipChanges()
{
using (var context = new BreakAwayContext())
{
var hawaii = (from d in context.Destinations
where d.Name == "Hawaii"
select d).Single();
var davesDump = (from l in context.Lodgings
where l.Name == "Dave's Dump"
select l).Single();
context.Entry(davesDump)
Using Snapshot Change Tracking | 63
.Reference(l => l.Destination)
.Load();
hawaii.Lodgings.Add(davesDump);
Console.WriteLine(
"Before DetectChanges: {0}",
davesDump.Destination.Name);
context.ChangeTracker.DetectChanges();
Console.WriteLine(
"After DetectChanges: {0}",
davesDump.Destination.Name);
}
}
The coue loaus the Hawaii Destination into memoiy as well as Dave`s Dump Lodg
ing. It also uses explicit loauing to loau the Destination ol Dave`s Dumpthat`s the
Gianu Canyon. Dave`s Dump has such a Lau ieputation that he has ueciueu to move
the Dump to Hawaii wheie noLouy`s heaiu ol him yet. So we auu the davesDump instance
to the Lodgings collection ol hawaii. Because we aie using POCO oLjects, Entity Fiame-
woik uoesn`t know that we`ve maue this change, anu theieloie it uoesn`t lix up the
navigation piopeity oi loieign key piopeity on davesDump. Ve coulu wait until we call
SaveChanges, oi any othei methou, which tiiggeis DetectChanges, Lut peihaps we want
things lixeu up iight away. Ve`ve auueu in a call to DetectChanges to achieve this. Il
we upuate the Main methou to call DetectRelationshipChanges anu iun the application
we see this in action:
Before DetectChanges: Grand Canyon
After DetectChanges: Hawaii
Beloie the DetectChanges call, Dave`s Dump is still assigneu to the olu Destination.
Altei we call DetectChanges, ielationship lix-up has occuiieu anu eveiything is Lack in
sync.
Enabling and Working with Change Tracking Proxies
Il youi peiloimance piolilei has pinpointeu excessive calls to DetectChanges as a pioL-
lem oi you pielei ielationship lix-up to occui in ieal time, theie is anothei optionthe
change tiacking pioxies mentioneu eailiei. Vith only some minoi changes to youi
POCO classes, Entity Fiamewoik will Le aLle to cieate change tiacking pioxies. Change
tiacking pioxies will allow Entity Fiamewoik to tiack changes as we make them to oui
oLjects anu also peiloim ielationship lix-up as it uetects changes to ielationships.
The iules loi allowing a change tiacking pioxy to Le cieateu aie as lollows:
The class must Le public anu not sealed.
Each piopeity must Le maikeu as virtual.
64 | Chapter 3:Adding, Changing, and Deleting Entities
Each piopeity must have a puLlic gettei anu settei.
Any collection navigation piopeities must Le typeu as ICollection<T>.
Upuate the Destination class as shown in Example 3-17 to meet these ieguiiements.
Notice that we aie also iemoving the logic liom the constiuctoi that initializeu the
Lodgings piopeity. The change tiacking pioxy will oveiiiue any collection navigation
piopeities anu use its own collection type (EntityCollection<TEntity>). This collection
type will tiack any changes to the collection anu iepoit them to the change tiackei. Il
you attempt to assign anothei type to the piopeity, such as the List<T> we weie cieating
in the constiuctoi, the pioxy will thiow an exception.
Exanp|c 3-17. Dcstination c|ass updatcd to cnab|c changc trac|ing proxics
[Table("Locations", Schema = "baga")]
public class Destination
{
public Destination()
{
//this.Lodgings = new List<Lodging>();
}
[Column("LocationID")]
public virtual int DestinationId { get; set; }
[Required, Column("LocationName")]
[MaxLength(200)]
public virtual string Name { get; set; }
public virtual string Country { get; set; }
[MaxLength(500)]
public virtual string Description { get; set; }
[Column(TypeName = "image")]
public virtual byte[] Photo { get; set; }
public virtual string TravelWarnings { get; set; }
public virtual string ClimateInfo { get; set; }
public virtual ICollection<Lodging> Lodgings { get; set; }
}
In Chaptei 2, you leaineu how Entity Fiamewoik cieates uynamic pioxies loi a class
when one oi moie navigation piopeities in that class aie maikeu viitual. Those pioxies,
which ueiive liom the given class, allow the viitual navigation piopeities to Le lazy
loaueu. The change tiacking pioxies aie cieateu in the same way at iuntime, Lut these
pioxies have moie leatuies than those you saw in Chaptei 2.
Vhile the ieguiiements loi getting a change tiacking pioxy aie laiily simple, it`s also
veiy easy to miss one ol them. It`s even easiei to make a change to the class in the lutuie
that will unintentionally Lieak one ol the iules. Because ol this, it`s a goou iuea to auu
a unit test that ensuies Entity Fiamewoik can cieate a change tiacking pioxy. Let`s auu
a methou that will test just this (Example 3-1S). You`ll also neeu to auu a using loi the
System.Data.Objects.DataClasses namespace.
Enabling and Working with Change Tracking Proxies | 65
Exanp|c 3-18. Tcsting jor a changc trac|ing proxy
private static void TestForChangeTrackingProxy()
{
using (var context = new BreakAwayContext())
{
var destination = context.Destinations.First();
var isProxy = destination is IEntityWithChangeTracker;
Console.WriteLine("Destination is a proxy: {0}", isProxy);
}
}
Vhen Entity Fiamewoik cieates the uynamic pioxy loi change tiacking, it will imple-
ment the IEntityWithChangeTracker inteilace. The test in Example 3-1S cieates a Des
tination instance Ly ietiieving it liom the uataLase anu then checks loi this inteilace
to ensuie that the Destination is wiappeu with a change tiacking pioxy. Note that it`s
not enough just to check that Entity Fiamewoik is cieating a pioxy class that ueiives
liom oui class, Lecause lazy loauing pioxies will also uo this. The piesence ol IEntity
WithChangeTracker is what causes Entity Fiamewoik to listen loi changes in ieal time.
Now that we have a change tiacking pioxy, let`s upuate Main to call the ManualDe
tectChanges methou we wiote Lack in Example 3-1+ anu iun the application:
Before DetectChanges: Modified
After DetectChanges: Modified
This time we see that Entity Fiamewoik is awaie ol changes iegaiuless ol whethei
DetectChanges is calleu oi not. Now upuate Main to call the DetectRelationship
Changes methou we wiote in Example 3-16 anu iun the application:
Before DetectChanges: Hawaii
After DetectChanges: Hawaii
This time we see that Entity Fiamewoik uetecteu the ielationship change anu pei-
loimeu ielationship lix-up without DetectChanges Leing calleu.
It is not necessaiy to uisaLle automatic DetectChanges when you use
change tiacking pioxies. DetectChanges will skip the change uetection
piocess loi any oLjects that iepoit changes in ieal time. Theieloie, en-
aLling change tiacking pioxies is enough to get the peiloimance Lenelits
ol avoiuing DetectChanges. In lact, Entity Fiamewoik won`t even take a
snapshot ol the piopeity values when it linus a change tiacking pioxy.
DetectChanges knows it can skip scanning loi changes in entities that
uon`t have a snapshot ol theii oiiginal values.
66 | Chapter 3:Adding, Changing, and Deleting Entities
Il you have entities that contain complex types (loi example, Per
son.Address), Entity Fiamewoik will still use snapshot change tiacking
loi the piopeities containeu in the complex type. This is ieguiieu Le-
cause Entity Fiamewoik uoes not cieate a pioxy loi the complex type
instance. You still get the Lenelits ol automatic change uetection on the
piopeities uelineu uiiectly on the entity itsell, Lut changes to piopeities
on the complex type will only Le uetecteu Ly DetectChanges.
Ensuring the New Instances Get Proxies
Entity Fiamewoik will automatically cieate pioxies loi the iesults ol any gueiies you
iun. Howevei, il you just use the constiuctoi ol youi POCO class to cieate new oLjects,
these will not Le pioxies. In oiuei to get pioxies you neeu to use the DbSet.Create
methou to get new instances ol an entity. This iule is the same as when woiking with
POCOs with ObjectContext anu ObjectSet.
Il you have enaLleu change tiacking pioxies loi an entity in youi mouel,
you can still cieate anu auu nonpioxy instances ol the entity. Entity
Fiamewoik will happily woik with a mixtuie ol pioxieu anu nonpioxieu
entities in the same set. You just neeu to Le awaie that you will not get
automatic change tiacking oi ielationship lix-up loi instances that aie
not change tiacking pioxies. Having a mixtuie ol pioxieu anu non-
pioxieu instances in the same set can Le conlusing, so it`s geneially iec-
ommenueu that you use DbSet.Create to cieate new instances so that
all entities in the set aie change tiacking pioxies.
Auu the CreatingNewProxies methou shown in Example 3-19 to see this in action.
Exanp|c 3-19. Crcating ncw proxy instanccs
private static void CreatingNewProxies()
{
using (var context = new BreakAwayContext())
{
var nonProxy = new Destination();
nonProxy.Name = "Non-proxy Destination";
nonProxy.Lodgings = new List<Lodging>();
var proxy = context.Destinations.Create();
proxy.Name = "Proxy Destination";
context.Destinations.Add(proxy);
context.Destinations.Add(nonProxy);
var davesDump = (from l in context.Lodgings
where l.Name == "Dave's Dump"
select l).Single();
context.Entry(davesDump)
Enabling and Working with Change Tracking Proxies | 67
.Reference(l => l.Destination)
.Load();
Console.WriteLine(
"Before changes: {0}",
davesDump.Destination.Name);
nonProxy.Lodgings.Add(davesDump);
Console.WriteLine(
"Added to non-proxy destination: {0}",
davesDump.Destination.Name);
proxy.Lodgings.Add(davesDump);
Console.WriteLine(
"Added to proxy destination: {0}",
davesDump.Destination.Name);
}
}
The coue staits Ly cieating two new Destination instances anu auuing them to the
Destinations set on the context. One ol these Destinations is just an instance ol oui
POCO class. The othei is cieateu using DbSet.Create anu is a change tiacking pioxy.
Next, the coue gueiies loi Dave`s Dump anu loaus the Destination that it cuiiently
Lelongs to using the Entry methou that you leaineu aLout in Chaptei 2. Ve then auu
Dave`s Dump to the Lodgings piopeity ol the POCO Destination anu then to the pioxy
Destination. At each stage the coue piints out the name ol the Destination assigneu to
the Destination piopeity on Dave`s Dump:
Before changes: Grand Canyon
Added to non-proxy destination: Grand Canyon
Added to proxy destination: Proxy Destination
You can see that Dave`s Dump is initially assigneu to the Gianu Canyon Destination.
Vhen it`s auueu to the Lodgings collection ol the nonpioxy Destination, the Destina
tion piopeity on Dave`s Dump is not upuateu; it`s still the Gianu Canyon. Because this
Destination isn`t a pioxy, Entity Fiamewoik isn`t awaie that we changeu the ielation-
ship. Howevei, when we auu it to the Lodgings collection ol the pioxy Destination we
get lull ielationship lix-up instantly.
Creating Proxy Instances for Derived Types
Theie is also a geneiic oveiloau ol DbSet.Create that is useu to cieate instances ol
ueiiveu classes in oui set. Foi example, calling Create on the Lodgings set will give you
an instance ol the Lodging class. But the Lodgings set can also contain instances ol
Resort, which ueiives liom Lodging. To get a new pioxy instance ol Resort, we use the
geneiic oveiloau:
var newResort = context.Lodgings.Create<Resort>();
68 | Chapter 3:Adding, Changing, and Deleting Entities
Fetching Entities Without Change Tracking
You`ve pioLaLly gatheieu Ly now that tiacking changes isn`t a tiivial piocess anu theie
is a Lit ol oveiheau involveu. In some aieas ol youi application, you may Le uisplaying
uata in a ieau-only scieen. Because the uata will nevei get upuateu, you may want to
avoiu the oveiheau associateu with change tiacking.
Foitunately Entity Fiamewoik incluues an AsNoTracking methou that can Le useu to
execute a no-trac|ing qucry. A no-tiacking gueiy is simply a gueiy wheie the iesults
will not Le tiackeu loi changes Ly the context. Auu the PrintDestinationsWithoutCh
angeTracking methou shown in Example 3-20.
Exanp|c 3-20. Qucrying data without changc trac|ing
private static void PrintDestinationsWithoutChangeTracking()
{
using (var context = new BreakAwayContext())
{
foreach (var destination in context.Destinations.AsNoTracking())
{
Console.WriteLine(destination.Name);
}
}
}
The coue uses the AsNoTracking methou to get a no-tiacking gueiy loi the contents ol
the Destinations set. The iesults aie then iteiateu ovei anu piinteu to the console.
Because this is a no-tiacking gueiy, the context is not keeping tiack ol any changes
maue to the Destinations. Il you weie to mouily a piopeity ol one ol the Destina
tions anu call SaveChanges, the changes woulu not Le sent to the uataLase.
Fetching uata without change tiacking will usually only pioviue a no-
ticeaLle peiloimance gain when you aie letching laigei amounts ol uata
loi ieau-only uisplay. Il youi application will upuate anu save any ol the
uata, you shoulu not use AsNoTracking theie.
AsNoTracking is an extension methou uelineu on IQueryable<T>, so you can use it in
LINQ gueiies also. You can use AsNoTracking on the enu ol the DbSet in the from line
ol the gueiy:
var query = from d in context.Destinations.AsNoTracking()
where d.Country == "Australia"
select d;
You can also use AsNoTracking to conveit an existing LINQ gueiy to Le a no-tiacking
gueiy. Note that the coue uoesn`t just call AsNoTracking on the existing gueiy Lut ovei-
iiues the query vaiiaLle with the iesult ol the AsNoTracking call. This is ieguiieu Lecause
AsNoTracking uoesn`t mouily the gueiy that it is calleu on, it ietuins a new gueiy:
Fetching Entities Without Change Tracking | 69
var query = from d in context.Destinations
where d.Country == "Australia"
select d;
query = query.AsNoTracking();
Because AsNoTracking is an extension methou, you will neeu to have the Sys
tem.Data.Entity namespace impoiteu to use it.
70 | Chapter 3:Adding, Changing, and Deleting Entities
CHAPTER 4
Working with Disconnected Entities
Including N-Tier Applications
In the pievious chaptei you leaineu how to auu new entities anu change oi uelete
existing entities. All the examples we lookeu at involveu making changes one at a time
to entities that aie tiackeu Ly the context. Each ol the changes allecteu a single entity
oi ielationship. You saw that you can peiloim multiple ol these single entity opeiations
anu then call SaveChanges to push all the changes to the uataLase in a single tiansaction.
In this chaptei we will look at making changes to entities that aie not Leing tiackeu Ly
a context. Entities that aie not Leing tiackeu Ly a context aie known as disconncctcd
cntitics.
Foi most single-tiei applications, wheie the usei inteilace anu uataLase access layeis
iun in the same application piocess, you will pioLaLly just Le peiloiming opeiations
on entities that aie Leing tiackeu Ly a context. Opeiations on uisconnecteu entities aie
much moie common in N-Tiei applications. N-Tiei applications involve letching some
uata on a seivei anu ietuining it, ovei the netwoik, to a client machine. The client
application then manipulates this uata Leloie ietuining it to the seivei to Le peisisteu.
The N-Tiei pattein makes uata access moie complex Lecause theie is no longei a con-
text tiacking changes that aie maue to each entity. The uata is letcheu using one context,
anu ietuineu to the client wheie theie is no context to tiack changes. The uata is then
sent Lack to the seivei anu must Le peisisteu Lack to the uataLase using a new instance
ol the context.
Vhile the majoiity ol content in this chaptei is aimeu at uevelopeis
wiiting N-Tiei applications, it`s uselul inloimation loi anyone woiking
with Entity Fiamewoik anu will give you a ueepei unueistanuing ol how
Entity Fiamewoik Lehaves.
Vhen it comes time to peisist the uata on the seivei, you aie typically woiking with a
graph oj cntitics. A giaph ol entities is simply a numLei ol entities that ieleience each
71
othei. Ve`ve alieauy woikeu with giaphs ol entities that aie attacheu to the context.
In the last chaptei we lookeu at auuing a ielationship using a navigation piopeity, which
is enough to cieate a giaph, Lecause one entity now ieleiences anothei. In N-Tiei sce-
naiios this giaph ol entities is usually uisconnecteu liom the context, though, meaning
the context isn`t yet tiacking any ol the entities in the giaph.
Vhen it comes time to stait peiloiming opeiations on this uisconnecteu giaph, theie
aie some auuitional Lehaviois in Entity Fiamewoik that you neeu to Le awaie ol. The
entity that you peiloim the opeiation on is known as the root ol the giaph. Peiloiming
an opeiation on the ioot ol uisconnecteu giaph can have siue ellects on the iest ol the
giaph, too.
A Simple Operation on a Disconnected Graph
Beloie we uelve into the complexities ol N-Tiei scenaiios, let`s take a guick look at an
example ol the siue ellects ol peiloiming an opeiation on the ioot ol a uisconnecteu
giaph. In the pievious chaptei we saw that DbSet.Add can Le useu to iegistei a new
entity to Le inseiteu when SaveChanges is calleu.
You`ll see the teim rcgistcr useu thioughout this chaptei. Vhen an en-
tity is iegisteieu with the context it means that the context Lecomes
awaie ol the entity anu staits tiacking it.
So lai the entities we`ve passeu to the Add methou have Leen stanualone instances with
no ieleiences to othei entities. Now let`s see what happens when we pass the ioot ol a
newly cieateu giaph ol entities that isn`t yet tiackeu Ly the context. Auu the AddSim
pleGraph methou that is shown in Example +-1.
Exanp|c 1-1. Mcthod to add a graph oj cntitics
private static void AddSimpleGraph()
{
var essex = new Destination
{
Name = "Essex, Vermont",
Lodgings = new List<Lodging>
{
new Lodging { Name = "Big Essex Hotel" },
new Lodging { Name = "Essex Junction B&B" },
}
};
using (var context = new BreakAwayContext())
{
context.Destinations.Add(essex);
Console.WriteLine(
72 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
"Essex Destination: {0}",
context.Entry(essex).State);
foreach (var lodging in essex.Lodgings)
{
Console.WriteLine(
"{0}: {1}",
lodging.Name,
context.Entry(lodging).State);
}
context.SaveChanges();
}
}
The coue constiucts a new Destination instance, which also ieleiences two new Lodg
ing instances in its Lodgings piopeity. Then the new Destination is auueu to a context
using the Add methou. Once the Destination is auueu, the coue uses the DbCon
text.Entry methou to get access to the change tiacking inloimation that Entity Fiame-
woik has aLout the new Destination. Fiom this change tiacking inloimation the
State piopeity is useu to wiite out the cuiient state ol the entity. This piocess is then
iepeateu loi each ol the newly cieateu Lodgings that aie ieleienceu liom the new Des
tination. Il you mouily the Main methou to call AddSimpleGraph anu iun the application
you will see the lollowing output:
Essex Destination: Added
Big Essex Hotel: Added
Essex Junction B&B: Added
It`s no suipiise that Entity Fiamewoik has the new Destination iegisteieu as an
Added entity Lecause we useu the Add methou to auu it to the context. Vhat may Le a
little less oLvious is that Entity Fiamewoik lookeu in the navigation piopeities ol the
Destination instance anu saw that it ieleienceu two Lodging instances that the context
wasn`t alieauy tiacking. Entity Fiamewoik also iegisteis these entities as Added anu will
inseit them into the uataLase when SaveChanges is calleu. The piocess ol linuing ielateu
entities is iecuisive, so il one ol the new Lodging instances ieleienceu a new Person
instance, the Person woulu also get auueu to the context. Figuie +-1 attempts to visu-
alize how calling Add on a uisconnecteu Destination will also auu othei uisconnecteu
entities that aie ieachaLle liom the Destination.
Il a ieleience is lounu to an entity that is alieauy tiackeu Ly the context, the entity that
is alieauy tiackeu is lelt in its cuiient state. Foi example, il one ol oui new Lodging
instances ieleienceu an existing Person that hau Leen gueiieu loi using the context, the
existing Person woulu not Le maikeu as Added. The existing Person woulu iemain in the
Unchanged state anu the Lodging woulu Le inseiteu with its loieign key pointing to the
existing Person. Figuie +-2 attempts to visualize how auuing a uisconnecteu Destina
tion will also auu othei uisconnecteu entities, Lut il an entity that is Leing tiackeu Ly
the context is lounu, it is lelt in its cuiient state.
A Simple Operation on a Disconnected Graph | 73
Exploring the Challenges of N-Tier
Now that you`ve seen how Entity Fiamewoik Lehaves when it is askeu to auu the ioot
ol a giaph ol entities to the context, we`ie going to take a look at how this allects N-
Tiei applications. RememLei that in an N-Tiei application the uata is gueiieu loi in
one context, Lut the changes neeu to Le peisisteu using anothei context. Foi example,
on the seivei siue ol youi application you coulu expose a GetDestinationAndLodgings
methou that will ietuin a Destination with all ol its Lodgings:
public Destination GetDestinationAndLodgings(int destinationId)
The client siue ol the application coulu then letch a Destination anu make some
changes to it. These changes coulu incluue changing the Description ol the Destina
tion anu auuing a new Lodging to its Lodgings collection. The seivei siue ol the appli-
cation coulu then expose a SaveDestinationAndLodgings methou to push all these
changes Lack to the uataLase:
public void SaveDestinationAndLodgings(Destination destination)
Vhen it comes time to implement the SaveDestinationAndLodgings methou, things get
a little moie complicateu. Like the simple giaph liom Example +-1, the Destination
that gets passeu in isn`t tiackeu Ly a context. In lact, Lecause it`s Leen seiializeu ovei
Iigurc 1-1. Adding a disconncctcd graph oj cntitics
74 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
the netwoik on its way to anu liom the client, it`s not even the same instance that was
gueiieu loi using Entity Fiamewoik. The SaveDestinationAndLodgings methou neeus
to let Entity Fiamewoik know il it`s an existing Destination oi a new Destination that
neeus to Le auueu. Il it`s an existing Destination, some ol the piopeities may have Leen
mouilieu anu theieloie neeu to Le upuateu in the uataLase. The Lodgings piopeity may
also contain instances ol Lodging that can also Le existing oi new. Since Entity Fiame-
woik hasn`t Leen tiacking these entities, you neeu to let it know all ol this
inloimation.
Youi seivei-siue logic coulu Le exposeu thiough a seivice oi peihaps a class that lollows
the rcpository pattcrn. Eithei way, you neeu to oveicome the uisconnecteu natuie ol
the oLjects that aie ietuineu liom the client application.
Theie aie a numLei ol uilleient appioaches to solving the challenges associateu with
Luiluing N-Tiei applications. Coveiing the uetails ol each appioach is well Leyonu the
scope ol this Look, Lut we`ll take a guick look at the uilleient options that aie availaLle
to you anu how they ielate to using Entity Fiamewoik.
Iigurc 1-2. Adding a disconncctcd graph that rcjcrcnccs a trac|cd cntity
Exploring the Challenges of N-Tier | 75
Using Existing N-Tier Frameworks That Support Graph Modification
In a lot ol cases you can save youisell the heauache ol uealing with the intiicacies ol N-
Tiei uata access Ly using a liamewoik that takes caie ol tiacking changes that aie maue
to uata on the client anu applying those changes on the seivei. Entity Fiamewoik is
tightly integiateu with VCF Data Seivices, which is one such liamewoik. VCF Data
Seivices is Miciosolt`s solution to the N-Tiei challenge. Theie aie liamewoiks availaLle
liom othei venuois as well.
VCF Data Seivices allows you to choose what uata liom youi mouel is exposeu liom
the seivei anu what peimission clients have loi the uata (ieau, appenu, upuate, anu so
on). VCF Data Seivices also incluues a client component that takes caie ol tiacking
changes you make to uata on the client, pushing those changes Lack to the uataLase,
anu saving them using youi Entity Fiamewoik mouel. VCF Data Seivices uses the
OData piotocol (http://odata.org) loi exposing uata liom the seivei; this allows youi
seivice to Le accesseu Ly clients othei than VCF Data Seivices, incluuing non .NET
Fiamewoik platloims.
The VCF Data Seivices client has a similai look anu leel to peiloiming uata access
uiiectly against DbContext. Using VCF Data Seivices is aiguaLly the simplest appioach
to N-Tiei uata access with Entity Fiamewoik, anu it is the appioach the Entity Fiame-
woik team iecommenus. You can leain moie aLout using VCF Data Seivices with
DbContext in this aiticle: http://nsdn.nicrosojt.con/cn-us/data/hh272551.
VCF Data Seivices is a goou option loi Luiluing N-Tiei applications, Lut theie aie
some ieasons it may not Le the iight tool loi youi joL. VCF Data Seivices gives you
guite a Lit ol contiol ovei how ieguests aie piocesseu on the seivei, Lut it is a liamewoik
anu theieloie it is somewhat scopeu in what it can hanule. Depenuing on youi appli-
cation, you may have some auvanceu ieguiiements that aie not suppoiteu.
You may also Le in a situation wheie the shape ol the seivei opeiations anu/oi the
piotocol to Le useu loi client-seivei communication aie outsiue ol youi contiol. In
these situations, authoiing youi own weL seivices may Le the only viaLle option.
Self-Tracking Entities
Aiounu the time that Entity Fiamewoik + was ieleaseu, the Entity Fiamewoik team
also ieleaseu a Sell-Tiacking Entities template loi Mouel Fiist anu DataLase Fiist. This
ieplaceu the uelault coue geneiation with a template that piouuceu entities that woulu
inteinally tiack theii changes on the client anu tianslei the inloimation Lack to the
seivei. The template also geneiateu some helpei methous that woulu take the change
tiacking inloimation anu ieplay the changes Lack into a context on the seivei.
The Entity Fiamewoik team hasn`t maue any signilicant upuates to the Sell-Tiacking
Entities template since it was liist ieleaseu. They aie iecommenuing that uevelopeis
look at using VCF Data Seivices as a moie ioLust anu complete solution.
76 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
Theie is no Sell-Tiacking Entities template that can Le useu with the DLContext API.
Theie is also no Sell-Tiacking Entities solution loi use with Coue Fiist.
Using Explicit Operations on the Server Side
Anothei way to avoiu the complexity ol ueteimining the changes to Le maue on the
seivei is to expose veiy gianulai opeiations that ieguiie the client to iuentily the exact
change to Le maue. Foi example, iathei than the SaveDestinationAndLodgings methou
we saw eailiei in this chaptei, you coulu expose AddDestination anu UpdateDestina
tion methous. These methous woulu only opeiate on a stanualone Destination instance
iathei than a giaph ol entities. You woulu expose sepaiate methous loi auuing anu
upuating Locations. Entity Fiamewoik makes it simple to implement these methous
anu you`ll linu eveiything you neeu loi this appioach in the Unueistanuing How
DLContext Responus to Setting the State ol a Single Entity on page 7S section ol
this chaptei.
Vhile this option makes the seivei component ol youi application much easiei, it
potentially makes the client component moie complex. You aie also likely to enu up
with a laige numLei ol opeiations exposeu liom the seivei. Il you can`t use an existing
liamewoik, such as VCF Data Seivices, you will neeu to weigh up the Lenelits ol this
appioach anu the appioach coveieu in the next section. Gianulai opeiations will typ-
ically give you a highei guantity ol coue, Lut that coue will Le much simplei to wiite,
test, anu ueLug. Next we`ll take a look at a moie geneializeu solution that will pioLaLly
involve less coue, Lut is inheiently moie complex.
Replaying Changes on the Server
Anothei option is to have a moie geneializeu seivei opeiation that accepts a giaph ol
entities anu then lets Entity Fiamewoik know what state each entity in that giaph
shoulu Le in. This is olten ieleiieu to as rcp|aying the changes on the seivei, Lecause
you aie walking thiough each entity anu letting Entity Fiamewoik know what hap-
peneu on the client. The piocess ol iteiating thiough each entity anu setting its state is
also known as painting thc statc.
Theie aie many uilleient ways to implement this logic. You coulu wiite coue that is
stiongly tieu to the classes in youi mouel anu each seivei opeiation knows how to
navigate the giaph that is passeu in, look at each entity, anu set its state appiopiiately.
You can also come up with a geneializeu appioach that can ieplay the changes loi any
giaph given any ioot entity. The geneializeu appioach typically uses a Lase class oi an
inteilace that exposes inloimation to allow youi seivei-siue coue to woik out what
state each entity is in. You`ll see examples ol Loth ol these in the next section ol this
chaptei.
Exploring the Challenges of N-Tier | 77
Chaptei 1S ol Progranning Entity Irancwor|, 2c, uemonstiateu this
pattein Ly pioviuing a Lase class with a State piopeity. The State piop-
eity coulu Le set on the client siue anu then ieau on the seivei siue to
ueteimine how to iegistei the class with the context.
Understanding How DbContext Responds to Setting the State
of a Single Entity
Now that we`ve taken a look at the N-Tiei challenges anu the high-level options loi
auuiessing them, it`s time to see how to implement those technigues using Entity
Fiamewoik. Ve`ll stait Ly taking a step Lack anu looking at how to set the state loi a
single entity. Then we`ll take those technigues anu use them to set the state loi each
entity in a giaph.
Builuing an actual N-Tiei application is Leyonu the scope ol this Look, so we`ie going
to keep woiking in the console application. Ve`ll implement methous that you coulu
potentially expose liom a weL seivice loi youi client application to consume. Rathei
than seiializing entities, you`ll use a tempoiaiy context to letch any uata liom the ua-
taLase anu then manipulate the uata to mimic client-siue changes. You`ll then pass
these oLjects into the pseuuo-seivei-siue opeiations that will use a new context, with
no pievious knowleuge ol the entity instances, to peisist the changes to the uataLase.
Entity Fiamewoik has a list ol states that the change tiackei uses to iecoiu the status
ol each entity. In this section, you`ll see how to move an entity into each ol these states.
The states that Entity Fiamewoik uses when change tiacking aie the lollowing:
Addcd
The entity is Leing tiackeu Ly the context Lut uoes not exist in the uataLase. Duiing
SaveChanges an INSERT statement will Le useu to auu this entity to the uataLase.
Unchangcd
The entity alieauy exists in the uataLase anu has not Leen mouilieu since it was
ietiieveu liom the uataLase. SaveChanges uoes not neeu to piocess the entity.
Modijicd
The entity alieauy exists in the uataLase anu has Leen mouilieu in memoiy. Duiing
SaveChanges an UPDATE statement will Le sent to the uataLase to apply the changes.
A list ol the piopeities that have Leen mouilieu is also kept to ueteimine which
columns shoulu Le set in the UPDATE statement.
Dc|ctcd
The entity alieauy exists in the uataLase anu has Leen maikeu loi ueletion. Duiing
SaveChanges a DELETE statement will Le useu to iemove the entity liom the uataLase.
Dctachcd
The entity is not Leing tiackeu Ly the context.
78 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
Setting an entity to the Detached state useu to Le impoitant Leloie the
Entity Fiamewoik suppoiteu POCO oLjects. Piioi to POCO suppoit,
youi entities woulu have ieleiences to the context that was tiacking
them. These ieleiences woulu cause issues when tiying to attach an
entity to a seconu context. Setting an entity to the Detached state cleais
out all the ieleiences to the context anu also cleais the navigation piop-
eities ol the entityso that it no longei ieleiences any entities Leing
tiackeu Ly the context. Now that you can use POCO oLjects that uon`t
contain ieleiences to the context that is tiacking them, theie is iaiely a
neeu to move an entity to the Detached state. Ve will not Le coveiing
the Detached state in the iest ol this chaptei.
Marking a New Entity as Added
AiguaLly the simplest opeiation is to take an entity anu maik it as Added; in lact, you
saw how to uo that in Chaptei 3. You can use the DbSet.Add methou to tell Entity
Fiamewoik that an entity is auueu. Auu the AddDestination anu TestAddDestination
methous shown in Example +-2.
Exanp|c 1-2. Adding a ncw Dcstination
private static void TestAddDestination()
{
var jacksonHole = new Destination
{
Name = "Jackson Hole, Wyoming",
Description = "Get your skis on."
};
AddDestination(jacksonHole);
}
private static void AddDestination(Destination destination)
{
using (var context = new BreakAwayContext())
{
context.Destinations.Add(destination);
context.SaveChanges();
}
}
The TestAddDestination methou mimics a client application cieating a new Destina
tion anu passing it to the AddDestination methou on the seivei. The AddDestination
methou auus the new Destination to a context anu then saves it to the uataLase. Il you
upuate the Main methou to call TestAddDestination anu iun the application, you will
see that the ]ackson Hole Destination is auueu to the uataLase.
Theie is also anothei way to wiite this same methou. Eailiei in this chaptei we saw that
we coulu use DbContext.Entry to get access to the change tiacking inloimation loi an
entity. Ve useu the State piopeity on the change tiacking inloimation to ieau the state
Understanding How DbContext Responds to Setting the State of a Single Entity | 79
ol an entity, Lut we can also set this piopeity, too. Upuate the AddDestination methou
to use the State piopeity iathei than DbSet.Add (Example +-3). You`ll neeu to auu a
using loi the System.Data namespace to use the EntityState enum.
Exanp|c 1-3. Mar|ing a Dcstination as addcd using thc Statc propcrty
private static void AddDestination(Destination destination)
{
using (var context = new BreakAwayContext())
{
context.Entry(destination).State = EntityState.Added;
context.SaveChanges();
}
}
Calling DbSet.Add anu setting the State to Added Loth achieve exactly the same thing.
Il the entity is not tiackeu Ly the context, it will stait Leing tiackeu Ly the context in
the Added state. Both DbSet.Add anu setting the State to Added aie giaph opeiations
meaning that any othei entities that aie not Leing tiackeu Ly the context anu aie ieach-
aLle liom the ioot entity will also Le maikeu as Auueu. Il the entity is alieauy tiackeu
Ly the context, it will Le moveu to the Added state. So lai we`ve only auueu entities that
aien`t tiackeu Ly the context, Lut a little latei in this chaptei you`ll see that Leing aLle
to set the state ol an entity that is alieauy tiackeu to Added is impoitant.
Vhethei you choose DbSet.Add oi setting the State piopeity is simply a mattei ol which
is moie convenient in the coue you aie wiiting. Foi this simple scenaiio, the coue is
aiguaLly easiei to unueistanu il you stick with DbSet.Add. Latei in the chaptei you`ll
see that setting the State piopeity is cleanei in geneializeu scenaiios wheie youi coue
calculates the state you aie setting the entity to.
Marking an Existing Entity as Unchanged
Vhile DbSet.Add is useu to tell Entity Fiamewoik aLout new entities, DbSet.Attach is
useu to tell Entity Fiamewoik aLout existing entities. The Attach methou will maik an
entity in the Unchanged state. Auu the AttachDestination anu TestAttachDestination
methous shown in Example +-+.
Exanp|c 1-1. Attaching an cxisting Dcstination
private static void TestAttachDestination()
{
Destination canyon;
using (var context = new BreakAwayContext())
{
canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
select d).Single();
}
AttachDestination(canyon);
80 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
}
private static void AttachDestination(Destination destination)
{
using (var context = new BreakAwayContext())
{
context.Destinations.Attach(destination);
context.SaveChanges();
}
}
The TestAttachDestination methou letches an existing Destination liom the uataLase
anu passes it to the AttachDestination methou, which uses DbSet.Attach to iegistei the
existing Destination with a context anu save the changes. You can also wiite this
methou Ly setting the State piopeity loi the entity to Unchanged (Example +-5).
Exanp|c 1-5. Mar|ing a Dcstination as Unchangcd using thc Statc propcrty
private static void AttachDestination(Destination destination)
{
using (var context = new BreakAwayContext())
{
context.Entry(destination).State = EntityState.Unchanged;
context.SaveChanges();
}
}
Il you upuate the Main methou to call TestAttachDestination anu iun youi application,
you`ll uiscovei the AttachDestination is guite pointless Lecause it uoesn`t uo anything.
That`s Lecause we tolu Entity Fiamewoik that the Destination was Unchanged; theieloie
SaveChanges just ignoies the entity. Vhile this is a Lit silly il it`s all we uo in the methou,
it will Le veiy uselul when we have a giaph ol entities, some ol which may not neeu
any changes pusheu to the uataLase.
DbSet.Attach anu setting the State piopeity to Unchanged have the same ellect. Il the
entity isn`t tiackeu Ly the context, it will Legin Leing tiackeu in the Unchanged state. Il
it is alieauy tiackeu, it will Le moveu to the Unchanged state.
Marking an Existing Entity as Modified
You`ve seen how to maik an existing entity as Unchanged; now let`s look at existing
entities that have some changes that neeu to Le pusheu to the uataLase. Theie aie a lew
options that iange liom maiking eveiy piopeity as mouilieu to telling Entity Fiame-
woik what the oiiginal values weie loi each piopeity anu letting it calculate the mou-
ilications. Foi the moment we`ll just locus on getting the changes into the uataLase Ly
maiking the whole entity as mouilieu. You`ll leain aLout setting inuiviuual piopeities
as mouilieu in Tiacking Inuiviuually Mouilieu Piopeities on page 99.
Vhen you tell Entity Fiamewoik that an entiie entity is mouilieu, it will senu an upuate
statement to the uataLase that sets eveiy column to the values cuiiently stoieu in the
Understanding How DbContext Responds to Setting the State of a Single Entity | 81
piopeities ol the entity. Theie isn`t an AttachAsModified methou on DbSet, although
theie aie plenty ol people asking loi one, so uon`t Le suipiiseu il it tuins up in the
lutuie. Foi the moment, you neeu to set the State piopeity to Modified. Auu the
UpdateDestination anu TestUpdateDestination methous shown in Example +-6.
Exanp|c 1-. Attaching an cxisting cntity as nodijicd
private static void TestUpdateDestination()
{
Destination canyon;
using (var context = new BreakAwayContext())
{
canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
select d).Single();
}
canyon.TravelWarnings = "Don't fall in!";
UpdateDestination(canyon);
}
private static void UpdateDestination(Destination destination)
{
using (var context = new BreakAwayContext())
{
context.Entry(destination).State = EntityState.Modified;
context.SaveChanges();
}
}
The TestUpdateDestination simulates a client application that gueiies loi the Gianu
Canyon Destination liom the seivei, mouilies the TravelWarnings piopeity, anu passes
the upuateu Destination to the UpdateDestination methou on the seivei. The Update
Destination methou maiks the incoming Destination as Modified anu saves the changes
to the uataLase. Il you upuate the Main methou to call TestModifyDestination anu iun
the application, an upuate statement is sent to the uataLase:
exec sp_executesql N'
update
[baga].[Locations]
set
[LocationName] = @0,
[Country] = @1,
[Description] = null,
[Photo] = null,
[TravelWarnings] = @2,
[ClimateInfo] = null
where
([LocationID] = @3)
',N'@0 nvarchar(200),@1 nvarchar(max) ,@2 nvarchar(max) ,@3 int',
@0=N'Grand Canyon',@1=N'USA',@2=N'Don''t fall in!',@3=1
82 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
Notice that even though we only upuateu the TravelWarnings piopeity, Entity Fiame-
woik is upuating eveiy column. TravelWarnings gets upuateu to the new value we set;
all the othei columns get upuateu to the same values they hau when we ietiieveu the
Destination liom the uataLase. This is Lecause Entity Fiamewoik uoesn`t know which
piopeities weie upuateu. Ve just specilieu that the entity was Modified, so Entity
Fiamewoik is upuating all the columns to match the cuiient piopeity values.
Registering an Existing Entity for Deletion
In the pievious chaptei you saw that DbSet.Remove can Le useu to uelete existing entities.
You also leaineu that calling Remove on an entity in the Added state will cancel the au-
uition anu cause the context to stop tiacking the entity. Calling Remove on an entity that
isn`t tiackeu Ly the context will cause an InvalidOperationException to Le thiown. The
Entity Fiamewoik thiows this exception Lecause it isn`t cleai whethei the entity you
aie tiying to iemove is an existing entity that shoulu Le maikeu loi ueletion oi a new
entity that shoulu just Le ignoieu. Foi this ieason, we can`t use just Remove to maik a
uisconnecteu entity as Deleteu; we neeu to Attach it liist. Auu the DeleteDestination
anu TestDeleteDestination methous shows in Example +-7.
Exanp|c 1-7. Rcgistcring an cxisting cntity jor dc|ction
private static void TestDeleteDestination()
{
Destination canyon;
using (var context = new BreakAwayContext())
{
canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
select d).Single();
}
DeleteDestination(canyon);
}
private static void DeleteDestination(Destination destination)
{
using (var context = new BreakAwayContext())
{
context.Destinations.Attach(destination);
context.Destinations.Remove(destination);
context.SaveChanges();
}
}
The TestDeleteDestination methou simulates a client application letching an existing
Destination liom the seivei anu then passing it to the DeleteDestination methou on
the seivei. The DeleteDestination methou uses the Attach methou to let the context
know that it`s an existing Destination. Then the Remove methou is useu to iegistei the
existing Destination loi ueletion.
Understanding How DbContext Responds to Setting the State of a Single Entity | 83
Having to attach the entity anu then uelete it is a little conlusing anu it`s not immeuiately
cleai what the coue is tiying to achieve when we look at it. Foitunately we can also set
the State piopeity ol the entity to Deleted. Because Remove is useu in attacheu scenaiios,
its Lehavioi is uilleient when useu on auueu, unchangeu, oi uisconnecteu entities.
Howevei, changing the State piopeity is only useu loi explicitly setting state ol an
entity, so Entity Fiamewoik assumes that setting the state to ueleteu means you want
an existing entity maikeu loi ueletion. Ve can iewiite the DeleteDestination methou
to use this appioach anu the intent ol the coue Lecomes a lot cleaiei (Example +-S).
Exanp|c 1-8. Rcgistcring an cntity jor dc|ction using thc Statc propcrty
private static void DeleteDestination(Destination destination)
{
using (var context = new BreakAwayContext())
{
context.Entry(destination).State = EntityState.Deleted;
context.SaveChanges();
}
}
Using a stub entity to mark for deletion
Entity Fiamewoik only neeus the key value(s) ol an entity to Le aLle to constiuct a
DELETE statement loi the entity. Theieloie, you can ieuuce the amount ol uata that gets
sent Letween the seivei anu client Ly only senuing Lack the key value ol entities that
neeu to Le ueleteu. Auu anothei oveiloau ol DeleteDestination that just accepts the
key ol the Destination to Le ueleteu (Example +-9).
Exanp|c 1-9. Rcgistcring an cntity jor dc|ction bascd on its |cy va|uc
private static void DeleteDestination(int destinationId)
{
using (var context = new BreakAwayContext())
{
var destination = new Destination { DestinationId = destinationId };
context.Entry(destination).State = EntityState.Deleted;
context.SaveChanges();
}
}
The coue constiucts a new Destination instance with only the key piopeity setthat`s
DestinationId. This entity with only the key value set is known as a stub cntity. The
coue then sets the State piopeity loi this new entity to Deleted, inuicating that it is an
existing entity to Le maikeu loi ueletion. Because Entity Fiamewoik will only access
the DestinationId piopeity when it constiucts the DELETE statement, it uoesn`t mattei
that the othei piopeities aie not populateu.
84 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
Il youi entity contains any concuiiency tokens, these piopeities aie also
useu to constiuct the DELETE statement. You can still use the stuL entity
appioach, Lut you will neeu to set values loi the concuiiency token
piopeities as well.
Working with Relationships with and Without Foreign Keys
Il theie is one thing you can uo to make youi lile easiei in N-Tiei scenaiios, it`s to expose
loieign key piopeities loi the ielationships in youi mouel. Relationships that incluue
a loieign key piopeity aie calleu jorcign |cy associations, anu unless you have a veiy
goou ieason not to expose the loieign key piopeities you will save youisell a lot ol pain
Ly incluuing them.
Moie uetaileu inloimation on incluuing loieign keys in youi Coue Fiist
mouel is availaLle in Chaptei + ol Progranning Entity Irancwor|: Codc
Iirst. Foi DataLase Fiist anu Mouel Fiist, see Chaptei 19 ol Progran-
ning Entity Irancwor|, 2c.
Benefiting from foreign key properties
The goou news is that il you incluue loieign key piopeities in youi mouel you alieauy
know eveiything you neeu to know to woik with ielationships. Il you maik the entity
as auueu, it will get inseiteu with the value cuiiently assigneu to its loieign key piopeity.
Il you maik an entity as mouilieu, the loieign key piopeity will get upuateu along with
all the othei piopeities. To see this in action, auu the UpdateLodging anu TestUpdate
Lodging methous shown in Example +-10.
Exanp|c 1-10. Changing a jorcign |cy va|uc
private static void TestUpdateLodging()
{
int reefId;
Lodging davesDump;
using (var context = new BreakAwayContext())
{
reefId = (from d in context.Destinations
where d.Name == "Great Barrier Reef"
select d.DestinationId).Single();
davesDump = (from l in context.Lodgings
where l.Name == "Dave's Dump"
select l).Single();
}
davesDump.DestinationId = reefId;
UpdateLodging(davesDump);
}
private static void UpdateLodging(Lodging lodging)
Understanding How DbContext Responds to Setting the State of a Single Entity | 85
{
using (var context = new BreakAwayContext())
{
context.Entry(lodging).State = EntityState.Modified;
context.SaveChanges();
}
}
The TestUpdateLodging methou simulates a client application that letches a Lodging anu
changes its DestinationId piopeity. RememLei that DestinationId is the loieign key
to the Destination that the Lodging Lelongs to. This time it looks like Dave`s ieputation
has Lecome national, so he is moving his Lusiness aiounu the woilu liom Hawaii, USA,
to Queenslanu, Austialia. Once the changes aie maue, the Lodging is passeu to the
UpdateLodging methou on the seivei. This methou looks veiy much like the UpdateDes
tination methou you wiote Lack in Example +-6. As you can see, theie is nothing
special ieguiieu to ueal with loieign key ielationships.
Using navigation properties to define relationships
You aien`t iestiicteu to using the loieign key piopeity to change ielationships. You can
still use the navigation piopeities, anu you`ll see this in action a little latei in this
chaptei.
Il you chose not to incluue a loieign key piopeity, you aie using indcpcndcnt associa-
tions. They aie calleu inuepenuent associations Lecause Entity Fiamewoik ieasons
aLout the ielationship inuepenuently ol the entities that the ielationship Lelongs to,
anu this makes things uillicult when it comes to uisconnecteu entities. In lact, loieign
keys aie so vital in N-Tiei scenaiios that the Entity Fiamewoik team chose not to expose
the methous loi changing the state ol inuepenuent ielationships on the DLContext API.
To woik with them, you will neeu to uiop uown to the OLjectContext API.
Delving into the complexities ol inuepenuent associations in N-Tiei
scenaiios is well Leyonu the scope ol this Look. You can linu a uetaileu
look at this topic in Chaptei 19 ol Piogiamming Entity Fiamewoik, 2e.
The pioLlem you`ll encountei is that change tiacking only tiacks scalai piopeities.
Vhen you change a loieign key piopeity, such as Lodging.DestinationId, the context
is awaie ol that piopeity. But when you change a navigation piopeity, theie`s nothing
to tiack. Even il you maik the Lodging as Modified, the context is only awaie ol the
scalai piopeities. Vhen you use an inuepenuent association, the context actually keeps
tiack ol the ielationship itsell. It has an oLject that contains the keys ol the two ielateu
instances anu this is what the context uses to tiack ielationship mouilications anu
upuate them in the uataLase anu the state ol the ielationship. Vhen youi entity is not
connecteu to the context, the context is unaLle to uo the woik ol mouilying these
ielationship oLjects. Vhen you ieconnect the entities to a context, you neeu to
86 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
manually uig uown into the context, linu those ielationship oLjects, anu mouily them.
It`s pietty complex anu veiy conlusing. This is one ol the ieasons uevelopeis convinceu
the Entity Fiamewoik team that we ieally neeueu to have loieign key piopeities avail-
aLle to us altei stiuggling with inuepenuent associations in Entity Fiamewoik 1.0.
So, given that you simply might linu youisell in this same situation, let`s take a look at
a guick example to give you an iuea ol the uilleience in iesolving this pioLlem in a
uisconnecteu scenaiio to the simplicity ol woiking with loieign key associations, anu
hopelully convince you to stay away liom them. Let`s assume loi a moment that we
haun`t incluueu the DestinationId piopeity on Lodging anu useu an inuepenuent as-
sociation insteau. The coue loi UpdateLodging woulu neeu to look something like
Example +-11.
Exanp|c 1-11. UpdatcLodging jor indcpcndcnt associations
private static void UpdateLodging(
Lodging lodging,
Destination previousDestination)
{
using (var context = new BreakAwayContext())
{
context.Entry(lodging).State = EntityState.Modified;
context.Entry(lodging.Destination).State = EntityState.Unchanged;

if (lodging.Destination.DestinationId !=
previousDestination.DestinationId)
{
context.Entry(previousDestination).State = EntityState.Unchanged;
((IObjectContextAdapter)context).ObjectContext
.ObjectStateManager
.ChangeRelationshipState(
lodging,
lodging.Destination,
l => l.Destination,
EntityState.Added);
((IObjectContextAdapter)context).ObjectContext
.ObjectStateManager
.ChangeRelationshipState(
lodging,
previousDestination,
l => l.Destination,
EntityState.Deleted);
}
context.SaveChanges();
}
}
The liist thing you`ll notice is that we now ieguiie the Destination instance that the
Lodging useu to Lelong to. This is Lecause changing an inuepenuent association ieguiies
Understanding How DbContext Responds to Setting the State of a Single Entity | 87
that the context have an auueu ielationship to the new entity anu a ueleteu ielationship
to the pievious entity. This is a complicateu siue ellect ol the way that Entity Fiamewoik
hanules concuiiency checks when upuating inuepenuent associations. The coue staits
Ly maiking the louging as Modified, to take caie ol upuating any piopeities that aien`t
involveu in the ielationship. The cuiient Destination is also maikeu as an existing
entity. The coue then checks to see il this call is changing the Destination this Lodg
ing is assigneu to, Ly compaiing the cuiient Destination anu the pievious Destina
tion. Il the Destination uoes neeu to Le changeu, the pievious Destination is also
maikeu as an existing entity. The coue then uses ObjectContext.ObjectStateMan
ager.ChangeRelationshipState to maik the ielationship to the cuiient Destination as
Added anu the pievious Destination as Deleted. Vith all that taken caie ol, it`s time to
call SaveChanges anu push the changes to the uataLase.
Many-to-many ielationships aie always inuepenuent associations. Il
you have many-to-many ielationships in youi mouel, you will neeu to
use ChangeRelationshipState to maik ieleiences as Added, Unchanged, oi
Deleted when piocessing changes on the seivei.
Setting the State for Multiple Entities in an Entity Graph
Now that you know the lunuamental Luiluing Llocks, it`s time to plug them togethei
to ueteimine anu set the state ol each entity in a giaph. Vhen a uisconnecteu entity
giaph aiiives on the seivei siue, the seivei will not know the state ol the entities. You
neeu to pioviue a way loi the state to Le uiscoveieu so that the context can Le maue
awaie ol each entity`s state. This section will uemonstiate how you can coeice the
context to inlei anu then apply entity state.
The liist step is to get the giaph into the context. You uo that Ly peiloiming an opei-
ation that will cause the context to stait tiacking the ioot ol the giaph. Once that is
uone, you can set the state loi each entity in the giaph.
Getting the Graph into the Context
Back in Example +-1 you saw that auuing the ioot ol a giaph will cause eveiy entity in
the giaph to Le iegisteieu with the context as a new entity. This Lehavioi is the same
il you use DbSet.Add oi change the State piopeity loi an entity to Added. Once all the
entities aie tiackeu Ly the state managei, you can then woik youi way aiounu the giaph,
specilying the coiiect state loi each entity. It is possiLle to stait Ly calling an opeiation
that will iegistei the ioot as an existing entity. This incluues DbSet.Attach oi setting the
State piopeity to Unchanged, Modified, oi Deleted. Howevei, this appioach isn`t iec-
ommenueu Lecause you iun the iisk ol exceptions uue to uuplicate key values il you
have auueu entities in youi giaph. Il you iegistei the ioot as an existing entity, eveiy
entity in the giaph will get iegisteieu as an existing entity. Because existing entities
88 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
shoulu all have unigue piimaiy keys, Entity Fiamewoik will ensuie that you uon`t
iegistei two existing entities ol the same type with the same key. Il you have a new
entity instance that will have its piimaiy key value geneiateu Ly the uataLase, you
pioLaLly won`t Lothei assigning a value to the piimaiy key; you`ll just leave it set to
the uelault value. This means il youi giaph contains multiple new entities ol the same
type, they will have the same key value. Il you attach the ioot, Entity Fiamewoik will
attempt to maik eveiy entity as Unchanged, which will lail Lecause you woulu have two
existing entities with the same key.
Foi example, assume you have an existing Destination that incluues two new instances
ol the Lodging class in its Lodgings piopeity. The key ol Lodging is the integei piopeity
LodgingId, which is geneiateu Ly the uataLase when you save. This means the two new
Lodgings Loth have zeio assigneu to theii LodgingId piopeity. Il you attempt to iegistei
the ioot as an existing entity, the two Lodging instances will also Le iegisteieu as existing
entities. This will lail Lecause that woulu mean theie aie two existing Lodgings with a
key ol zeio.
Theie may Le some cases wheie you have multiple giaphs anu/oi inuiviuual entities
that neeu to Le iegisteieu with the context. This occuis when not all the entities you
neeu to ieason aLout aie ieachaLle liom one ioot. Foi example, we aie going to Le
wiiting a methou that will save a Destination anu the Lodgings that it ieleiences. Each
ol these entities will eithei Le a new entity to Le auueu oi an existing entity to Le
upuateu. The methou will also accept a sepaiate list ol Lodgings that shoulu Le ueleteu.
Because these Lodgings aie to Le ueleteu, the client will pioLaLly have iemoveu them
liom the Lodgings collection on the ioot Destination. Theieloie iegisteiing the ioot
Destination won`t Le enough to iegistei the ueleteu Lodgings; we`ll neeu to iegistei
them sepaiately.
TaLle +-1 summaiizes the options along with the pios anu cons that you`ve ieau in this
section.
Tab|c 1-1. Pattcrns and warnings jor joining graphs to a contcxt
Method Result Warnings
Add Root Every entity in graph will be change
tracked and marked with Added state
SaveChanges will attempt to insert data that may already exist
in database
Attach Root Every entity in graph will be change
tracked and marked with Unchanged
state
New entities will not get inserted into database and have a
conflict with matching keys
Add or Attach Root,
then paint state
throughout graph
Entities will have correct state when
painting is complete
It is recommended that you Add the root rather than attaching
it to avoid key conflicts for new entities. More information is
provided at the start of this section.
Setting the State for Multiple Entities in an Entity Graph | 89
Setting the State of Entities in a Graph
Ve`ie going to stait Ly looking at an example wheie we iteiate thiough the giaph using
oui knowleuge ol the mouel anu set the state loi each entity thioughout the giaph, oi
painting thc statc. In the next section you`ll see how you can geneialize this solution so
that you uon`t have to manually navigate the giaph set the state ol each entity. Ve`ie
going to wiite a methou that will save a Destination anu its ielateu Lodgings. Deleteu
entities aie tiicky in uisconnecteu scenaiios. Il you uelete the entity on the client siue,
theie`s nothing to senu to the seivei so that it knows to uelete that uata in the uataLase
as well. This example uemonstiates one pattein loi oveicoming the pioLlem. Auu the
SaveDestinationAndLodgings methou shown in Example +-12.
Exanp|c 1-12. Sctting statc jor cach cntity in a graph
private static void SaveDestinationAndLodgings(
Destination destination,
List<Lodging> deletedLodgings)
{
// TODO: Ensure only Destinations & Lodgings are passed in
using (var context = new BreakAwayContext())
{
context.Destinations.Add(destination);
if (destination.DestinationId > 0)
{
context.Entry(destination).State = EntityState.Modified;
}
foreach (var lodging in destination.Lodgings)
{
if (lodging.LodgingId > 0)
{
context.Entry(lodging).State = EntityState.Modified;
}
}
foreach (var lodging in deletedLodgings)
{
context.Entry(lodging).State = EntityState.Deleted;
}
context.SaveChanges();
}
}
The new methou accepts the Destination to Le saveu. This Destination may also have
Lodgings ielateu to it. The methou also accepts a list ol Lodgings to Le ueleteu. These
Lodgings may oi may not Le in the Lodgings collection ol the Destination that is Leing
saveu. You`ll also notice a TODO to ensuie that the client calling the methou only
supplieu Destinations anu Lodgings, Lecause that is all that oui methou is expecting.
90 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
Il the callei weie to ieleience an unexpecteu InternetSpecial liom one ol the Lodg
ings, we woulun`t piocess this with oui state setting logic. Valiuating input is goou
piactice anu isn`t ielateu to the topic at hanu, so we`ve lelt it out loi claiity.
The coue then auus the ioot Destination to the context, which will cause any ielateu
Lodgings to also Le auueu. Next we aie using a check on the key piopeity to ueteimine
il this is a new oi existing Destination. Il the key is set to zeio, it`s assumeu it`s a new
Destination anu it`s lelt in the auueu state; il it has a value, it`s maikeu as a mouilieu
entity to Le upuateu in the uataLase. The same piocess is then iepeateu loi each ol the
Lodgings that is ieleienceu liom the Destination.
Finally the Lodgings that aie to Le ueleteu aie iegisteieu in the Deleted state. Il these
Lodgings aie still ieleienceu liom the Destination, they aie alieauy in the state managei
in the auueu state. Il they weie not ieleienceu Ly the Destination, the context isn`t yet
awaie ol them. Eithei way, changing the state to Deleted will iegistei them loi ueletion.
Vith the state appiopiiately set loi eveiy entity in the giaph, it`s time to call Save
Changes.
To see the SaveDestinationAndLodgings methou in action, auu the TestSaveDestinatio
nAndLodgings methou shown in Example +-13.
Exanp|c 1-13. Mcthod to tcst SavcDcstinationAndLodging ncthod
private static void TestSaveDestinationAndLodgings()
{
Destination canyon;
using (var context = new BreakAwayContext())
{
canyon = (from d in context.Destinations.Include(d => d.Lodgings)
where d.Name == "Grand Canyon"
select d).Single();
}
canyon.TravelWarnings = "Carry enough water!";
canyon.Lodgings.Add(new Lodging
{
Name = "Big Canyon Lodge"
});
var firstLodging = canyon.Lodgings.ElementAt(0);
firstLodging.Name = "New Name Holiday Park";
var secondLodging = canyon.Lodgings.ElementAt(1);
var deletedLodgings = new List<Lodging>();
canyon.Lodgings.Remove(secondLodging);
deletedLodgings.Add(secondLodging);
SaveDestinationAndLodgings(canyon, deletedLodgings);
}
Setting the State for Multiple Entities in an Entity Graph | 91
This methou ietiieves the Gianu Canyon Destination liom the uataLase, using eagei
loauing to ensuie that the ielateu Lodgings aie also in memoiy. Next it changes the
TravelWarnings piopeity ol the canyon. Then one ol the Lodgings is mouilieu anu an-
othei is iemoveu liom the Lodgings piopeity ol the canyon anu auueu to a list ol Lodg
ings to Le ueleteu. A new Lodging is also auueu to the canyon. Finally the canyon anu
the list ol Lodgings to Le ueleteu aie passeu to the SaveDestinationAndLodgings methou.
Il you upuate the Main methou to call TestSaveDestinationAndLodgings anu iun the
application, a seiies ol SQL statements will Le sent to the uataLase (Figuie +-3).
Iigurc 1-3. SQL statcncnts during savc ajtcr sctting statc jor cach cntity
The liist update is loi the existing Gianu Canyon Destination that we upuateu the
TravelWarnings piopeity on. Next is the update loi the Lodging we changeu the name
ol. Then comes the delete loi the Lodging we auueu to the list ol Lodgings to Le ueleteu.
Finally, we see the insert loi the new Lodging we cieateu anu auueu to the Lodgings
collection ol the Gianu Canyon Destination.
Building a Generic Approach to Track State Locally
The SaveDestination methou we implementeu in Example +-12 isn`t oveily complex,
Lut il we expose methous to save vaiious paits ol oui mouel, we woulu Le iepeating
the state setting coue ovei anu ovei again in each methou. So let`s take a look at a moie
geneializeu appioach to applying changes on the seivei.
You may iecognize this pattein liom Progranning Entity Irancwor|,
2c. It was intiouuceu in Chaptei 1S when uemonstiating the usei ol
POCOs in VCF Seivices.
This appioach ielies on having a consistent way to ueteimine the state ol any entity in
youi mouel. The easiest way to achieve that is to have an inteilace oi abstract Lase
class that eveiy entity in youi mouel will implement.
Interface Versus Base Class
The uecision Letween using a Lase class oi an inteilace is entiiely up to you. Using a
Lase class means you ueline the auuitional piopeities in one place anu then each entity
just neeus to inheiit liom the Lase class. Some uevelopeis may pielei the inteilace
Lecause having a common Lase class loi eveiy entity pollutes the shape ol the mouel.
92 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
As coauthois we uiscusseu which appioach to use in the Look anu lounu that theie
wasn`t a cleai winnei. Ve opteu to use an inteilace, Lut the technigues you will leain
in this chaptei woik just as well il you choose to use a Lase class.
Foi this example we aie going to use an IObjectWithState inteilace that will tell us the
cuiient state ol the entity. It will have no uepenuencies on Entity Fiamewoik anu will
Le implementeu Ly youi uomain classes, so it can go in the Model pioject. Go aheau
anu auu the IObjectWithState inteilace to the Model pioject (Example +-1+). Latei in
this section you`ll auu this inteilace to some ol youi classes.
Exanp|c 1-11. Sanp|c intcrjacc to dctcrninc statc oj an cntity
namespace Model
{
public interface IObjectWithState
{
State State { get; set; }
}
public enum State
{
Added,
Unchanged,
Modified,
Deleted
}
}
Note that we`ve opteu loi a new enum to iepiesent state iathei than ieusing the Enti
tyState enum liom Entity Fiamewoik. This ensuies oui uomain mouel uoesn`t have
any uepenuencies on types liom oui uata access technology.
Beloie we get to applying state, it woulu Le uselul il any entities we ietiieve liom the
uataLase woulu have theii state automatically set to Unchanged. Otheiwise the seivei
neeus to manually uo this Leloie ietuining the oLjects to the client. The easiest way to
uo this is to listen to the ObjectMaterialized event on the unueilying ObjectContext.
Auu the constiuctoi in Example +-15 to the BreakAwayContext class. You`ll neeu to auu
a using statement loi the System.Data.Entity.Infrastructure namespace to get access
to the IObjectContextAdapter inteilace.
Exanp|c 1-15. Hoo|ing up an cvcnt to nar| cxisting cntitics as Unchangcd
public BreakAwayContext()
{
((IObjectContextAdapter)this).ObjectContext
.ObjectMaterialized += (sender, args) =>
{
var entity = args.Entity as IObjectWithState;
if (entity != null)
{
entity.State = State.Unchanged;
Building a Generic Approach to Track State Locally | 93
}
};
}
The coue uses IObjectContextAdapter to get access to the unueilying ObjectContext. It
then wiies up a new hanulei to the ObjectMaterialized event, which will liie whenevei
an entity is ietuineu liom a gueiy to the uataLase. Because all oLjects that come liom
the uataLase aie existing oLjects, we take this oppoitunity to maik them as Unchanged
il they implement oui state tiacking inteilace.
In a ieal-woilu scenaiio you woulu neeu to implement the change tiacking inteilace
on eveiy class in youi mouel. But loi the sake ol simplicity, we will just use Destina
tion anu Lodging loi this uemonstiation. Go aheau anu euit the Lodging anu Destina
tion classes to implement the IObjectWithState inteilace:
public class Destination : IObjectWithState
public class Lodging : IObjectWithState
You`ll also neeu to auu a State piopeity into Loth ol these classes to satisly the IOb
jectWithState inteilace that you just auueu:
public State State { get; set; }
Now it`s time to wiite a methou that uses all this inloimation to take a uisconnecteu
giaph anu apply the client-siue changes to a context Ly setting the coiiect state loi each
entity in the giaph.
One impoitant thing to iememLei is that this appioach is uepenuent on
the client application honoiing the contiact ol setting the coiiect state.
Il the client uoesn`t set the coiiect state, the save piocess will not Lehave
coiiectly.
Auu the SaveDestinationGraph anu ConvertState methous shown in Example +-16.
Exanp|c 1-1. Sctting statc bascd on a statc trac|ing intcrjacc
public static void SaveDestinationGraph(Destination destination)
{
using (var context = new BreakAwayContext())
{
context.Destinations.Add(destination);
foreach (var entry in context.ChangeTracker
.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
}
context.SaveChanges();
}
94 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
}
public static EntityState ConvertState(State state)
{
switch (state)
{
case State.Added:
return EntityState.Added;
case State.Modified:
return EntityState.Modified;
case State.Deleted:
return EntityState.Deleted;
default:
return EntityState.Unchanged;
}
}
The coue uses DbSet.Add on the ioot Destination to get the contents ol the giaph into
the context in the Added state. Next it uses the ChangeTracker.Entries<TEntity> methou
to linu the entiies loi all entities that aie tiackeu Ly the context anu implement
IObjectWithState.
The Entries methou will give you access to the same oLject that you
woulu get Ly calling DbContext.Entry on each entity. Theie is a nonge-
neiic oveiloau ol Entries that will give you an entiy loi eveiy entity that
is tiackeu Ly the context. The geneiic oveiloau, which we aie using, will
liltei the entiies to those that aie ol the specilieu type, ueiiveu liom the
specilieu type, oi implement the specilieu inteilace. Il you use the
geneiic oveiloau, the Entity piopeity ol each entiy oLject will Le
stiongly typeu as the type you specilieu. You`ll leain moie aLout the
Entries methou in Chaptei 5.
Foi each entiy, the coue conveits the state liom the entities State piopeity to Entity
Fiamewoik`s EntityState anu sets it to the State piopeity loi the change tiackei entiy.
Once all the states have Leen set, it`s time to use SaveChanges to push the changes to
the uataLase. Now that we have oui geneializeu solution, let`s wiite some coue to test
it out. Ve`ie going to apply the same changes we uiu Lack in Example +-13, Lut this
time using oui new methou ol applying changes. Auu the TestSaveDestinationGraph
methou shown in Example +-17.
Exanp|c 1-17. Tcsting thc ncw SavcDcstinationTcst ncthod
private static void TestSaveDestinationGraph()
{
Destination canyon;
using (var context = new BreakAwayContext())
{
Building a Generic Approach to Track State Locally | 95
canyon = (from d in context.Destinations.Include(d => d.Lodgings)
where d.Name == "Grand Canyon"
select d).Single();
}
canyon.TravelWarnings = "Carry enough water!";
canyon.State = State.Modified;
var firstLodging = canyon.Lodgings.First();
firstLodging.Name = "New Name Holiday Park";
firstLodging.State = State.Modified;
var secondLodging = canyon.Lodgings.Last();
secondLodging.State = State.Deleted;
canyon.Lodgings.Add(new Lodging
{
Name = "Big Canyon Lodge",
State = State.Added
});
SaveDestinationGraph(canyon);
}
The coue simulates a client application that gueiies loi an existing Destination anu its
ielateu Lodgings. The Destination is upuateu anu maikeu as Modified. The liist Lodg
ing is also upuateu anu maikeu as Modified. The seconu Lodging is maikeu loi ueletion
Ly setting its State piopeity to Deleted. Finally, a new Lodging is put into the Lodg
ings collection with its State set to Added. The giaph is then passeu to the SaveDestina
tionGraph methou. Il you upuate the Main methou to call TestSaveDestinationGraph
anu iun youi application, the same SQL statements liom Figuie +-3 will Le iun against
the uataLase.
Creating a Generic Method That Can Apply State Through Any Graph
Vith some simple tweaks to the SaveDestinationGraph methou we wiote in Exam-
ple +-16, we can cieate a methou that can woik on any ioot in oui mouel, not just
Destinations. Auu the ApplyChanges methou shown in Example +-1S.
The geneiic methou uemonstiateu in this section is specilically uesigneu
loi use with uisconnecteu scenaiios wheie you have a shoit-liveu con-
text. Notice that the context is instantiateu in the ApplyChanges methou.
Exanp|c 1-18. Gcncra|izcd ncthod jor rcp|aying changcs jron a disconncctcd graph oj cntitics
private static void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new BreakAwayContext())
{
96 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
context.Set<TEntity>().Add(root);
foreach (var entry in context.ChangeTracker
.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
}
context.SaveChanges();
}
}
This new methou accepts any ioot that implements IObjectWithState. Because we
uon`t know the type ol the ioot until iuntime, we uon`t know which DbSet to auu it to.
Foitunately theie is a Set<T> methou on DbContext that can Le useu to cieate a set ol a
type that will Le iesolveu at iuntime. Ve use that to get a set anu then auu the ioot.
Next we set the state loi each entity in the giaph anu then push the changes to the
uataLase. Il you want to test this new methou out, change the last line ol youi TestSa
veDestinationGraph methou to call ApplyChanges iathei than SaveDestinationGraph:
ApplyChanges(canyon);
Running the application will iesult in the same SQL statements liom Figuie +-3 Leing
iun again.
Theie is one potential issue with oui ApplyChanges methouat the moment it Llinuly
assumes that eveiy entity in the giaph implements IObjectWithState. Il an entity that
uoesn`t implement the inteilace is piesent, it will just Le lelt in the Added state anu Entity
Fiamewoik will attempt to inseit it. Upuate the ApplyChanges methou as shown in
Example +-19.
Exanp|c 1-19. Chcc|ing jor cntitics that don`t inp|cncnt |ObjcctWithStatc
private static void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new BreakAwayContext())
{
context.Set<TEntity>().Add(root);
CheckForEntitiesWithoutStateInterface(context);
foreach (var entry in context.ChangeTracker
.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
}
context.SaveChanges();
}
}
Building a Generic Approach to Track State Locally | 97
private static void CheckForEntitiesWithoutStateInterface(
BreakAwayContext context)
{
var entitiesWithoutState =
from e in context.ChangeTracker.Entries()
where !(e.Entity is IObjectWithState)
select e;
if (entitiesWithoutState.Any())
{
throw new NotSupportedException(
"All entities must implement IObjectWithState");
}
}
The methou now calls a CheckForEntitiesWithoutStateInterface helpei methou that
uses the nongeneiic oveiloau ol ChangeTracker.Entries to get all entities that have Leen
auueu to the context. It uses a LINQ gueiy to linu any ol these that uon`t implement
IObjectWithState. Il any entities that uon`t implement IObjectWithState aie lounu, an
exception is thiown.
Concurrency Implications
This appioach woiks well with timestamp-style concuiiency tokens, wheie the piop-
eity that is useu as a concuiiency token will not Le upuateu on the client. Foi existing
entities, the value ol the timestamp piopeity will Le sent to the client anu then Lack to
the seivei. The entity will then Le iegisteieu as Unchanged, Modified, oi Deleted with
the same value in the concuiiency token piopeity as when it was oiiginally gueiieu
liom the uataLase.
Il you neeu to have concuiiency checking in youi N-Tiei application, timestamp piop-
eities aie aiguaLly the easiest way to implement this. Moie inloimation on timestamp
piopeities in Coue Fiist mouels is availaLle in Chaptei 3 ol Progranning Entity Iranc-
wor|: Codc Iirst. Foi DataLase Fiist anu Mouel Fiist, see Chaptei 23 ol Progranning
Entity Irancwor|, 2c.
Il a piopeity that can Le upuateu on the client is useu as a concuiiency token, this
appioach will not sullice. Because this appioach uoes not tiack the oiiginal value ol
piopeities, the concuiiency token will only have the upuateu value loi the concuiiency
check. This value will Le checkeu against the uataLase value uuiing save, anu a con-
cuiiency exception will Le thiown Lecause it will not match the value in the uataLase.
To oveicome this limitation you will neeu to use the appioach uesciiLeu in Recoiuing
Oiiginal Values on page 102.
98 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
Tracking Individually Modified Properties
So lai, the methous you`ve seen have locuseu on changing the state ol an entity. Foi a
lot ol applications, it`s enough to simply tiack the state at the entity level anu upuate
entiie oLjects just as you woulu when ielying on stoieu pioceuuies. Howevei, you may
linu youisell wanting to Le moie gianulai with the way mouilieu piopeities aie tiackeu.
Rathei than maiking the entiie entity as mouilieu, you might want only the piopeities
that have actually changeu to Le maikeu as mouilieu. This is how change tiacking
woiks when piopeities ol a tiackeu entity aie mouilieu. It ensuies that only the piop-
eities that have actually Leen changeu woulu Le incluueu in the upuate statement. In
this section we`ll take a guick look at some common appioaches to achieving gianulai
piopeity tiacking when the entities aie uisconnecteu liom the context:
Keeping tiack ol mouilieu piopeities on the client siue anu passing that list to the
seivei along with the entities
Stoiing the oiiginal piopeities into the entities when they aie ietiieveu liom the
uataLase Leloie passing them onto the client
Regueiying the uataLase when the entities have Leen ietuineu to the seivei liom
the client
The samples pioviueu in this section will pioviue you with a closei look
at the Change Tiackei API that is tieu to the DLContext API. The Entity
Fiamewoik team woikeu haiu to make oui lives easiei when uealing
with uisconnecteu scenaiios. You`ve alieauy seen some ol the Lenelits
we uevelopeis can ieap liom theii woik, anu you`ll see moie as you ieau
thiough to the enu ol this chaptei.
Recording Modified Property Names
This liist appioach is veiy similai to tiacking state at the entity level. In auuition to
maiking an entity as mouilieu, the client is also iesponsiLle loi iecoiuing which piop-
eities have Leen mouilieu. One way to uo this woulu Le to auu a list ol mouilieu piop-
eity names to the state tiacking inteilace. Upuate the IObjectWithState inteilace to
incluue a ModifiedProperties piopeity, as shown in Example +-20.
Exanp|c 1-20. Statc trac|ing intcrjacc updatcd to inc|udc nodijicd propcrtics
using System.Collections.Generic;
namespace Model
{
public interface IObjectWithState
{
State State { get; set; }
List<string> ModifiedProperties { get; set; }
}
Tracking Individually Modified Properties | 99
public enum State
{
Added,
Unchanged,
Modified,
Deleted
}
}
You`ll also neeu to auu this piopeity to the Destination anu Lodging classes to satisly
this new auuition to the IObjectWithState inteilace:
public List<string> ModifiedProperties { get; set; }
Now that we have a place to iecoiu the mouilieu piopeities, let`s upuate the Apply
Changes methou to make use ol it, as shown in Example +-21.
Exanp|c 1-21. Updating App|yChangcs to usc ModijicdPropcrtics
private static void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new BreakAwayContext())
{
context.Set<TEntity>().Add(root);
CheckForEntitiesWithoutStateInterface(context);
foreach (var entry in context.ChangeTracker
.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
if (stateInfo.State == State.Modified)
{
entry.State = EntityState.Unchanged;
foreach (var property in stateInfo.ModifiedProperties)
{
entry.Property(property).IsModified = true;
}
}
else
{
entry.State = ConvertState(stateInfo.State);
}
}
context.SaveChanges();
}
}
The changes to the methou aie insiue the foreach loop wheie we apply the state. Il we
linu a mouilieu entity in the giaph, we maik it as Unchanged, iathei than Modified. Once
the entity is maikeu as Unchanged, we loop thiough the mouilieu piopeities anu maik
100 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
each ol them as mouilieu. Ve uo this using the Property methou on the entiy to get
the change tiacking inloimation loi the given piopeity anu then setting the IsModi
fied piopeity to true. As soon as we maik one ol the piopeities as mouilieu, the state
ol the entity will also move to Modified. But only the piopeities we maikeu as mouilieu
will Le upuateu when we save.
Theie aie two oveiloaus ol Property, one that accepts the name ol the
piopeity as a stiing anu the othei that accepts a lamLua expiession iep-
iesenting the piopeity. Ve aie using the stiing piopeity Lecause we
uon`t know the names ol the piopeities we want to access until iuntime.
Il you know the name ol the piopeity you want to access, you can use
the lamLua veision so that you get compile-time checking ol the sup-
plieu value (loi example, context.Entry(destination).Property(d =>
d.Name).IsModified = true).
You'll also neeu to upuate the TestSaveDestinationGraph methou to populate Modified
Properties loi the Destination anu Lodging that aie mouilieu:
canyon.TravelWarnings = "Carry enough water!";
canyon.State = State.Modified;
canyon.ModifiedProperties = new List<string> { "TravelWarnings" };
var firstLodging = canyon.Lodgings.First();
firstLodging.Name = "New Name Holiday Park";
firstLodging.State = State.Modified;
firstLodging.ModifiedProperties = new List<string> { "Name" };
Il you iun the application again, you will see a set ol SQL statements similai to the ones
in Figuie +-3. This time, howevei, the update statements only set the piopeities we
maikeu as mouilieu. Heie is the SQL liom the upuate statement loi the Destination:
exec sp_executesql N'update [baga].[Locations]
set [TravelWarnings] = @0
where ([LocationID] = @1)
',N'@0 nvarchar(max) ,@1 int',
@0=N'Carry enough water!',@1=1
Complex Types
Entity Fiamewoik uses a single value to iecoiu whethei the piopeities stoieu insiue a
complex piopeity aie mouilieu oi not. Il any ol the piopeities aie mouilieu, the ioot
complex piopeity is maikeu as mouilieu anu all the piopeities insiue the complex
piopeity will Le set in the UPDATE statement that is issueu uuiing SaveChanges. Foi
example, il the Address.State piopeity is mouilieu on an existing Person, Entity Fiame-
woik iecoius the entiie Auuiess piopeity as mouilieu. All the piopeities associateu
with a Persons auuiess will Le incluueu in the UPDATE statement uuiing SaveChanges.
The coue shown in Example +-21 assumes that the client uses the same logic when
populating the ModifiedProperties collection. Il the Address.State piopeity is moui-
Tracking Individually Modified Properties | 101
lieu, the ModifiedProperties collection will just contain "Address". The ApplyChanges
coue will then use the change tiackei API to maik the Address piopeity as mouilieu.
Inloimation on woiking with the change tiacking inloimation ol inuiviuual piopeities
insiue a complex piopeity is pioviueu in Voiking with Complex Piopei-
ties on page 131.
Concurrency implications
This appioach has the same implications loi concuiiency as the geneiic appioach you
saw in the pievious section. Timestamp concuiiency piopeities will woik well, Lut
concuiiency piopeities that can Le mouilieu on the client will cause issues.
Recording Original Values
An alteinative to asking the client to iecoiu the piopeities that weie mouilieu is to keep
tiack ol the oiiginal values loi existing entities. One ol the Lig auvantages ol this ap-
pioach is that you aie no longei ielying on the client to tell you which piopeities weie
mouilieu. This makes the coue on the client siue much simplei anu less eiioi pione.
Entity Fiamewoik can then check loi changes Letween the oiiginal anu cuiient values
to ueteimine il anything is mouilieu. Let`s change the IObjectWithState inteilace to
iecoiu oiiginal values iathei than mouilieu piopeities. Because we aie going to calculate
whethei an entity is mouilieu oi not, we no longei neeu the Modified option in the
State enum, so let`s iemove that, too. These changes aie shown in Example +-22.
Exanp|c 1-22. Changc statc trac|ing intcrjacc to usc origina| va|ucs
using System.Collections.Generic;
namespace Model
{
public interface IObjectWithState
{
State State { get; set; }
Dictionary<string, object> OriginalValues { get; set; }
}
public enum State
{
Added,
Unchanged,
Deleted
}
}
You`ll also neeu to iemove the ModifiedProperties piopeity liom Destination anu
Lodging anu auu in the new OriginalValues piopeity:
public Dictionary<string, object> OriginalValues { get; set; }
102 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
Ve want Entity Fiamewoik to automatically populate the OriginalValues piopeity
when an entity is ietiieveu liom the uataLase, so let`s upuate the event hanulei we
auueu to the constiuctoi ol oui context (Example +-23). You`ll neeu to auu a using
statement loi the System.Collections.Generic namespace.
Exanp|c 1-23. Popu|ating origina| va|ucs ajtcr qucry
public BreakAwayContext()
{
((IObjectContextAdapter)this).ObjectContext
.ObjectMaterialized += (sender, args) =>
{
var entity = args.Entity as IObjectWithState;
if (entity != null)
{
entity.State = State.Unchanged;
entity.OriginalValues =
BuildOriginalValues(this.Entry(entity).OriginalValues);
}
};
}
private static Dictionary<string, object> BuildOriginalValues(
DbPropertyValues originalValues)
{
var result = new Dictionary<string, object>();
foreach (var propertyName in originalValues.PropertyNames)
{
var value = originalValues[propertyName];
if (value is DbPropertyValues)
{
result[propertyName] =
BuildOriginalValues((DbPropertyValues)value);
}
else
{
result[propertyName] = value;
}
}
return result;
}
In auuition to maiking the entity as Unchanged, this upuateu coue will populate the
OriginalValues piopeity. It uoes this Ly getting the oiiginal values liom the change
tiacking entiy loi the entity anu using the BuildOriginalValue helpei methou to conveit
them to the ieguiieu uictionaiy loimat. The helpei methou loops thiough each ol the
piopeities that we have oiiginal values loi. Il the value is just a noimal scalai piopeity,
it copies the value ol the piopeity to into the iesulting uictionaiy. Il the value is a
DbPropertyValues, this inuicates that it is a complex piopeity anu the coue uses a ie-
cuisive call to Luilu a nesteu uictionaiy ol the values in the complex piopeity. Moie
Tracking Individually Modified Properties | 103
inloimation on nesteu DbPropertyValues loi complex piopeities is availaLle in Voik-
ing with Complex Piopeities on page 131.
Because the entity has just Leen ietuineu liom the uataLase, the cuiient anu oiiginal
values aie the same, so we coulu have also useu the CurrentValues piopeity to get the
values. Now we can upuate the ApplyChanges methou to make use ol this piopeity
(Example +-2+).
Exanp|c 1-21. Using origina| va|ucs in App|yChangcs
private static void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new BreakAwayContext())
{
context.Set<TEntity>().Add(root);
CheckForEntitiesWithoutStateInterface(context);
foreach (var entry in context.ChangeTracker
.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
if (stateInfo.State == State.Unchanged)
{
ApplyPropertyChanges(entry.OriginalValues,
stateInfo.OriginalValues);
}
}
context.SaveChanges();
}
}
private static void ApplyPropertyChanges(
DbPropertyValues values,
Dictionary<string, object> originalValues)
{
foreach (var originalValue in originalValues)
{
if (originalValue.Value is Dictionary<string, object>)
{
ApplyPropertyChanges(
(DbPropertyValues)values[originalValue.Key],
(Dictionary<string, object>)originalValue.Value);
}
else
{
values[originalValue.Key] = originalValue.Value;
}
}
}
104 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
Altei painting the state ol entities thioughout the giaph, the coue now checks to see il
it`s an existing entity. Foi existing entities the coue uses the ApplyPropertyChanges
helpei methou to set the oiiginal values loi the entity. The helpei methou loops thiough
the OriginalValues that weie captuieu when the entity was ietiieveu liom the uataLase.
Il the value is a nesteu uictionaiy, inuicating a complex piopeity, then it uses a iecuisive
call to apply the changes loi the inuiviuual piopeities insiue the complex piopeity. Il
the value is just a scalai value, it upuates the oiiginal value Leing stoieu Ly the context.
Entity Fiamewoik will uetect il any ol the values uillei liom the values cuiiently as-
signeu to the piopeities ol the entity. Il a uilleience is uetecteu, the piopeity, anu
theieloie the entity, will Le maikeu as mouilieu. Ve also neeu to upuate the Convert
State methou Lecause we no longei have a Modified option in the State enum
(Example +-25).
Exanp|c 1-25. ConvcrtStatc updatcd to rcj|cct rcnova| oj Modijicd statc
public static EntityState ConvertState(State state)
{
switch (state)
{
case State.Added:
return EntityState.Added;
case State.Deleted:
return EntityState.Deleted;
default:
return EntityState.Unchanged;
}
}
To test out the new logic, you can upuate the TestSaveDestinationGraph methou to no
longei maik entities as Modified when it changes piopeities (Example +-26). This is no
longei ieguiieu Lecause the ApplyChanges methou will calculate this loi you.
Exanp|c 1-2. Updating thc tcst ncthod to tcst rccording oj origina| va|ucs
private static void TestSaveDestinationGraph()
{
Destination canyon;
using (var context = new BreakAwayContext())
{
canyon = (from d in context.Destinations.Include(d => d.Lodgings)
where d.Name == "Grand Canyon"
select d).Single();
}
canyon.TravelWarnings = "Carry enough water!";
var firstLodging = canyon.Lodgings.First();
firstLodging.Name = "New Name Holiday Park";
var secondLodging = canyon.Lodgings.Last();
Tracking Individually Modified Properties | 105
secondLodging.State = State.Deleted;
canyon.Lodgings.Add(new Lodging
{
Name = "Big Canyon Lodge",
State = State.Added
});
ApplyChanges(canyon);
}
Il you iun the application, you will get the lamiliai set ol SQL statements liom Fig-
uie +-3. The upuate statements that aie geneiateu will only set piopeities that weie
actually mouilieu.
Concurrency implications
This appioach olleis the Lest concuiiency suppoit Lecause it iecoius the same inloi-
mation that is stoieu Ly the change tiackei when mouilying entities that aie attacheu
to a context. Timestamp concuiiency piopeities will woik Lecause the value ietiieveu
liom the uataLase is sent to the client anu then Lack to the seivei, to Le useu when
upuating existing uata. Concuiiency piopeities that can Le mouilieu will also woik
Lecause the oiiginal value, which was assigneu to the piopeity when it was ietiieveu
liom the uataLase, is iecoiueu. Because this value is set as the oiiginal value loi the
piopeity, Entity Fiamewoik will use the oiiginal value when peiloiming concuiiency
checks.
Querying and Applying Changes
Anothei appioach that uevelopeis sometimes tiy is to calculate the mouilieu piopeities
Ly gueiying the uataLase to get the cuiient entity anu then copying the values liom the
incoming entity. Because this appioach ieguiies one gueiy to get the entity liom the
uataLase anu olten a seconu gueiy to upuate the uata, it`s usually slowei than just
maiking the entity as mouilieu anu upuating eveiy column. That saiu, senuing a lot ol
unnecessaiy upuates to the uataLase isn`t iueal. Il one ol the othei technigues in this
chaptei uoesn`t woik loi you, this may Le woith looking at.
Entity Fiamewoik makes it easy to copy the values liom one oLject to anothei. Putting
giaphs asiue loi a moment, we coulu implement an UpdateDestination methou as
lollows:
public static void UpdateDestination(Destination destination)
{
using (var context = new BreakAwayContext())
{
if (destination.DestinationId > 0)
{
var existingDestiantion = context.Destinations
.Find(destination.DestinationId);
106 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
context.Entry(existingDestiantion)
.CurrentValues
.SetValues(destination);
}
else
{
context.Destinations.Add(destination);
}
context.SaveChanges();
}
}
Il the Destination has a key value assigneu, it`s assumeu to Le an existing Destina
tion. The Find methou is useu to loau the Destination liom the uataLase. The SetVal
ues methou is useu on CurrentValues to copy values liom the incoming Destination to
the existing Destination liom the uataLase. Entity Fiamewoik will automatically uetect
il any ol the piopeity values aie uilleient. Il theie aie uilleiences, the appiopiiate piop-
eities will Le maikeu as mouilieu.
This appioach lalls uown when you stait woiking with giaphs, though. Let`s assume
the incoming Destination ieleiences a new Lodging. Ve can`t gueiy loi this Lodging
liom the uataLase, since it`s new, so we neeu to iegistei this Lodging loi auuition. The
pioLlem is il we tiy anu auu the Lodging to the context, it will also tiy anu auu any othei
entities that it ieleiences. Il the Lodging ieleiences an existing entity, it`s going to enu
up in the context in the auueu state. This gets veiy complicateu, Lecause we now want
to tiy anu take this existing entity Lack out ol the context so that we can gueiy loi the
entity liom the uataLase anu copy its values ovei.
Vhile it is technically possiLle to make this woik, the coue gets veiy complicateu.
Foitunately, theie is a Lettei alteinative. Rathei than getting the existing entity liom
the uataLase anu copying the values to it, we can attach the incoming entity anu then
set its oiiginal values to the values liom the uataLase. Upuate the ApplyChanges methou
as shown in Example +-27.
Exanp|c 1-27. App|yChangcs chcc|s jor nodijication using databasc va|ucs
private static void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new BreakAwayContext())
{
context.Set<TEntity>().Add(root);
CheckForEntitiesWithoutStateInterface(context);
foreach (var entry in context.ChangeTracker
.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
Tracking Individually Modified Properties | 107
if (stateInfo.State == State.Unchanged)
{
var databaseValues = entry.GetDatabaseValues();
entry.OriginalValues.SetValues(databaseValues);
}
}
context.SaveChanges();
}
}
Rathei than getting the oiiginal values that weie iecoiueu when the entity was ietiieveu
liom the uataLase, the coue now gets the cuiient oiiginal values liom the uataLase. In
lact, the OriginalValues piopeity on IObjectWithState is now no longei ieguiieu. The
uataLase values aie ietiieveu using the GetDatabaseValues methou. This methou ietuins
DbPropertyValues, which is the same type ietuineu liom the CurrentValues anu Origi
nalValues piopeity on an entity. The SetValues methou on DbPropertyValues will copy
the values liom any othei DbPropertyValues instance into itsell. Ve use this lunction-
ality to copy the uataLase values into the oiiginal values loi each entity. Il you iun the
application, SQL statements similai to those liom Figuie +-3 will Le executeu against
the uataLase. Howevei, this time the update statements will only set the piopeities that
weie actually changeu.
Concurrency implications
This appioach Lypasses concuiiency checks Lecause it iegueiies loi the uataLase values
just Leloie saving any changes. These new uataLase values aie useu in place ol the
values that weie oiiginally ietiieveu liom the uataLase when senuing uata to the client.
Il you aie using concuiiency tokens, this appioach to ieplaying changes on the seivei
is not suitaLle.
Caching Original Entities
Anothei appioach that uevelopeis sometimes use is to cache the oiiginal entities that
aie ietiieveu liom the context. These cacheu entities can then Le useu to calculate the
mouilieu piopeities when it comes time to save. This appioach is ellectively the same
as the Queiy anu Apply Changes methou, Lut it uses cacheu in-memoiy entities,
insteau ol gueiying the uataLase, to calculate changes.
This appioach may initially seem attiactive Lecause it ieuuces the iounu tiips to the
uataLase anu iemoves the neeu to incluue any state inloimation on the entities that aie
ietuineu to the client application. It is, howevei, veiy complex anu not iecommenueu
unless aLsolutely ieguiieu.
In an N-Tiei application, the seivei typically has no way to know which save uata
calls align with which get uata calls. Foi example, insiue the UpdateDestination
methou you uon`t know which call was useu to get the uata. This makes it haiu to know
which cacheu giaph to use loi compaiison. The two common solutions to this aie to
implement some loim ol session ID, oi to implement a seconu-level cache.
108 | Chapter 4:Working with Disconnected Entities Including N-Tier Applications
The session ID appioach involves ietuining a unigue iuentiliei to the client alongsiue
any uata that is ietuineu. Vhen the client wants to save changes to the uata, it must
also supply this unigue ID that can Le useu to locate the cacheu giaph loi compaiison.
This gets veiy complicateu when you stait to think aLout seivei laims wheie the get
uata anu save uata calls may Le piocesseu Ly uilleient physical seiveis. Also consiuei
long-iunning client applications wheie the usei might gueiy loi some uata, leave the
application open oveinight, anu then save in the moining.
The seconu-level cache appioach involves having a single in-memoiy cache that is useu
loi all ieguests on the seivei. Vhen uata is gueiieu liom the uataLase, it is stoieu in
the cache ieauy to Le useu loi compaiison. Because multiple gueiies may ietuin the
same uata, oi at least oveilapping uata, the cache neeus to take caie ol meiging iesults
so that uuplicate entities uon`t exist in the cache. Seconu-level caches can pioviue a
nice peiloimance impiovement Lecause you can also gueiy against the cache to avoiu
hitting the uataLase to ietuin uata. Entity Fiamewoik uoes not have Luilt-in suppoit
loi a seconu-level cache, so you neeu to uo a lot ol woik to wiie one up. This gets
paiticulaily complicateu when you stait to think aLout setting expiiation iules loi youi
uata so that stale uata uoesn`t iemain in the cache when the uataLase gets upuateu.
Il you want to get a ueep euucation on the pios anu cons ol caching with ORMs, check
out this aiticle on the Association loi Computing Machineiy (ACM) weLsite: Expos-
ing the ORM Cache: Familiaiity with ORM caching issues can help pievent peiloi-
mance pioLlems anu Lugs.
Tracking Individually Modified Properties | 109
CHAPTER 5
Change Tracker API
So lai you have seen how to use Entity Fiamewoik to gueiy loi uata liom the uataLase
anu save changes to those entities Lack to the uataLase. You`ve seen how Entity Fiame-
woik will keep tiack ol any changes you make to entities that aie Leing tiackeu Ly a
context. It is the iesponsiLility ol the Change Tiackei to keep tiack ol these changes as
you make them.
In this chaptei you will leain aLout using the Change Tiackei API to access the inloi-
mation that Entity Fiamewoik is stoiing aLout the entities it is tiacking. This inloi-
mation goes Leyonu the values stoieu in the piopeities ol youi entities anu incluues
the cuiient state ol the entity, the oiiginal values liom the uataLase, which piopeities
have Leen mouilieu, anu othei uata. The Change Tiackei API also gives you access to
auuitional opeiations that can Le peiloimeu on an entity, such as ieloauing its values
liom the uataLase to ensuie you have the latest uata.
You`ve alieauy seen Lits ol the Change Tiackei API in action in eailiei chapteis. In
Chaptei 2 you saw how to peiloim explicit loauing using the DbContext.Entry. In
Chaptei 3 you saw how to get the Change Tiackei to scan youi entities loi changes
using the DbContext.ChangeTracker.DetectChanges methou. In Chaptei + you saw how
to set the state ol an entity, maik inuiviuual piopeities as mouilieu, anu woik with
oiiginal values using the Entry methou. You also saw how to look at all entities Leing
tiackeu Ly the context using the DbContext.ChangeTracker.Entries methou.
Ve`ll stait this chaptei Ly taking a toui ol all the inloimation anu opeiations that aie
availaLle in the Change Tiackei API. Then we`ll wiap up the chaptei Ly looking at how
these opeiations can Le comLineu to save time logging anu iesolving concuiiency
conllicts.
Change Tracking Information and Operations for a Single Entity
The easiest way to get access to the change tiacking inloimation loi an entity is using
the Entry methou on DbContext. Entry ietuins a DbEntityEntry instance, which gives
you access to the inloimation anu opeiations availaLle loi the entity. Theie aie two
111
oveiloaus ol Entry. One is geneiic (Entry<TEntity>) anu will ietuin a stiongly typeu
DbEntityEntry<TEntity>:
public DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity)
The othei oveiloau is nongeneiic anu ietuins DbEntityEntry:
public DbEntityEntry Entry(object entity);
Both ol these pioviue access to exactly the same inloimation anu opeiations. Because
the stiongly typeu DbEntityEntry<TEntity> knows the type ol entity it iepiesents, it
allows you to use lamLua expiessions when uiilling into piopeity uetails, so that you
get IntelliSense anu auuitional compile-time checks. You uon`t neeu to woiiy aLout
selecting the coiiect oveiloauthe compilei will take caie ol this loi you. Il the entity
you pass to Entry is typeu as object, you will get the nongeneiic DbEntityEntry. Il the
entity you pass in is typeu as something moie specilic than object (loi example, Desti
nation), you will get the geneiic DbEntityEntry<TEntity>, wheie TEntity is the same
type as the entity you pass in. You`ll see Loth ol these oveiloaus in action in the next
couple ol sections.
Working with the State Property
One ol the coie pieces ol change tiacking inloimation is what state the entity is cuiiently
in: Added, Unchanged, Modified, oi Deleted. This can Le ueteimineu using the State
piopeity on DbEntityEntry. To see how this woiks, auu the PrintState methou shown
in Example 5-1.
Exanp|c 5-1. Rcading thc Statc propcrty
private static void PrintState()
{
using (var context = new BreakAwayContext())
{
var canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
select d).Single();
DbEntityEntry<Destination> entry = context.Entry(canyon);
Console.WriteLine("Before Edit: {0}", entry.State);
canyon.TravelWarnings = "Take lots of water.";
Console.WriteLine("After Edit: {0}", entry.State);
}
}
The coue ietiieves the Gianu Canyon uestination liom the uataLase anu then locates
the change tiacking entiy loi it. The canyon vaiiaLle is stiongly typeu as Destination,
so the compilei selects the geneiic oveiloau ol Entry anu we get a stiongly typeu DbEn
tityEntry<Destination> ietuineu. The coue then piints out the state ol canyon as ie-
coiueu in the change tiackei. Next, the TravelWarnings piopeity is mouilieu anu then
112 | Chapter 5:Change Tracker API
the State is piinteu out again. Upuate the Main methou to call the PrintState methou
anu iun the application. The console winuow will uisplay the lollowing:
Before Edit: Unchanged
After Edit: Modified
As expecteu, the canyon oLject is iepoiteu as Unchanged altei it is ietiieveu liom the
uataLase. Altei mouilying one ol its piopeities, the oLject is seen Ly the change tiackei
as Leing in the Modified state.
Back in Chaptei 3, we enaLleu Destination as a changc trac|ing proxy, meaning that
changes to any instances ol Destination aie iepoiteu to the context in ieal time. Entities
that aie not change tiacking pioxies ieguiie an explicit oi implicit call to DetectCh
anges to scan loi any changes to the oLject. Most ol the methous on DbContext will
automatically call DetectChanges loi you. Entry is one ol those methous. But ieauing
the State piopeity will not cause an automatic DetectChanges. Il Destination was not
a change tiacking pioxy, you woulu neeu to call DetectChanges altei setting the Trav
elWarnings piopeity to get the coiiect state iepoiteu. Moie inloimation on DetectCh
anges is availaLle in the Using Snapshot Change Tiacking section ol Chaptei 3. You
can avoiu the neeu to call DetectChanges Ly calling Entry each time you neeu the entiy,
iathei than keeping a ieleience to it. Foi example, iathei than stoiing the entiy in the
entry vaiiaLle as you uiu in Example 5-1, you coulu use Entry each time you want the
state:
Console.WriteLine("Before Edit: {0}", context.Entry(canyon).State);
canyon.TravelWarnings = "Take lots of water.";
Console.WriteLine("After Edit: {0}", context.Entry(canyon).State);
The State piopeity also exposes a puLlic settei, meaning you can assign
a state to an entity. Setting the state is uselul when you aie woiking with
uisconnecteu giaphs ol entitiestypically in N-Tiei scenaiios. Chap-
tei + ol this Look is ueuicateu to leaining aLout the vaiious ways to set
the state ol entities, incluuing setting the State piopeity.
Working with Current, Original, and Database Values
Along with the cuiient state ol an entity, DbEntityEntry gives you access to the entity`s
cuiient, oiiginal, anu uataLase values. The DbPropertyValues type is useu to iepiesent
each ol these sets ol piopeities. Cuiient values aie the values that aie cuiiently set in
the piopeities ol the entity. Oiiginal values aie the values loi each piopeity when the
entity was oiiginally attacheu to the context; loi example, when the entity was liist
ietiieveu liom the uataLase. DataLase values aie the values cuiiently stoieu in the ua-
taLase, which may have changeu since you gueiieu loi the entity. Accessing uataLase
values involves Entity Fiamewoik peiloiming a Lehinu-the-scenes gueiy loi you.
Working with Current, Original, and Database Values | 113
Theie is a Lug in Entity Fiamewoik +.1 anu +.2 that Llocks you liom
using the GetDatabaseValues methou loi an entity that is not in the same
namespace as youi context. The Entity Fiamewoik team has lixeu this
Lug in the Entity Fiamewoik +.3 ielease. Il you aie using Entity Fiame-
woik +.2 oi eailiei, you will neeu to mouily the namespace ol youi con-
text class to Le the same as youi uomain classes. Failuie to make this
change will iesult in an EntitySqlException il you attempt to ietiieve
the uataLase values loi an entity.
Let`s stait Ly wiiting a methou that will output these vaiious values loi any given
Lodging. Auu the PrintChangeTrackingInfo methou shown in Example 5-2.
Exanp|c 5-2. Printing changc trac|ing injo jor a Lodging
private static void PrintChangeTrackingInfo(
BreakAwayContext context,
Lodging entity)
{
var entry = context.Entry(entity);
Console.WriteLine(entry.Entity.Name);
Console.WriteLine("State: {0}", entry.State);
Console.WriteLine("\nCurrent Values:");
PrintPropertyValues(entry.CurrentValues);
Console.WriteLine("\nOriginal Values:");
PrintPropertyValues(entry.OriginalValues);
Console.WriteLine("\nDatabase Values:");
PrintPropertyValues(entry.GetDatabaseValues());
}
private static void PrintPropertyValues(DbPropertyValues values)
{
foreach (var propertyName in values.PropertyNames)
{
Console.WriteLine(" - {0}: {1}",
propertyName,
values[propertyName]);
}
}
The coue staits Ly looking up the change tiacking entiy loi the supplieu Lodging. Be-
cause the lodging vaiiaLle is stiongly typeu as Lodging, the compilei will select the
geneiic oveiloau ol Entry. The coue then piints out the Name ol the Lodging. Ol couise,
we coulu have just gotten the name liom the lodging vaiiaLle, Lut we aie using the
Entity piopeity on the entiy loi uemonstiation puiposes. Because the entiy is stiongly
typeu, the Entity piopeity pioviues stiongly typeu access to the Destination, which is
why we can call entry.Entity.Name.
114 | Chapter 5:Change Tracker API
The coue then loops thiough the cuiient, oiiginal, anu uataLase values anu wiites the
value loi each piopeity using the PrintPropertyValues helpei methou. These collec-
tions ol values aie all the DbPropertyValues type. Note that theie is no way to uiiectly
iteiate ovei the values, so you neeu to iteiate ovei the names ol the piopeities anu look
up the value loi each piopeity. GetDatabaseValues will senu a gueiy to the uataLase at
the time it is calleu, to ueteimine what values aie cuiiently stoieu in the uataLase. A
new gueiy will Le sent to the uataLase eveiy time you call the methou.
This methou loi accessing cuiient, oiiginal, anu uataLase values uses a
stiing to iuentily the piopeity to get the value loi. In Voiking with
Inuiviuual Piopeities on page 12S, you will leain aLout a stiongly ty-
peu way to specily the piopeity.
Now let`s wiite a methou to test out oui change tiacking logic. Go aheau anu auu the
PrintLodgingInfo methou shown in Example 5-3.
Exanp|c 5-3. Mcthod to tcst PrintChangcTrac|ing|njo
private static void PrintLodgingInfo()
{
using (var context = new BreakAwayContext())
{
var hotel = (from d in context.Lodgings
where d.Name == "Grand Hotel"
select d).Single();
hotel.Name = "Super Grand Hotel";
context.Database.ExecuteSqlCommand(
@"UPDATE Lodgings
SET Name = 'Not-So-Grand Hotel'
WHERE Name = 'Grand Hotel'");
PrintChangeTrackingInfo(context, hotel);
}
}
This new methou locates an existing Lodging liom the uataLase anu mouilies its Name
piopeity. The coue then uses Database.ExecuteSqlCommand to iun some iaw SQL to
mouily the name ol the Lodging in the uataLase. You`ll leain moie aLout executing iaw
SQL against the uataLase in Chaptei S. Finally, the hotel instance is passeu to oui
PrintChangeTrackingInfo methou. Upuate the Main methou to call PrintLodgingInfo
anu iun the application, which will output the lollowing to the console:
Super Grand Hotel
State: Modified
Current Values:
- LodgingId: 1
- Name: Super Grand Hotel
Working with Current, Original, and Database Values | 115
- Owner:
- MilesFromNearestAirport: 2.50
- DestinationId: 1
- PrimaryContactId:
- SecondaryContactId:
Original Values:
- LodgingId: 1
- Name: Grand Hotel
- Owner:
- MilesFromNearestAirport: 2.50
- DestinationId: 1
- PrimaryContactId:
- SecondaryContactId:
Database Values:
- LodgingId: 1
- Name: Not-So-Grand Hotel
- Owner:
- MilesFromNearestAirport: 2.50
- DestinationId: 1
- PrimaryContactId:
- SecondaryContactId:
As expecteu, the cuiient name ol the hotel is piinteu out. Since we changeu the Name,
the State ol the entity is uisplayeu as Modified. The cuiient value loi Name shows the
new name that we assigneu in PrintLodgingInfo (Supei Gianu Hotel). The oiiginal
value ol Name shows the value when we ietiieveu the Lodging liom the uataLase (Gianu
Hotel). The uataLase value ol Name shows the new value we assigneu in the uataLase
using the iaw SQL commanu, altei the hotel was ietiieveu liom the uataLase (Not-
So-Gianu Hotel).
The coue liom Example 5-2 woiks nicely loi existing entities, Lecause they have cui-
ient, oiiginal, anu uataLase values. But this isn`t tiue loi new entities oi entities that
aie maikeu loi ueletion. New entities uon`t have oiiginal values oi uataLase values.
Entity Fiamewoik uoesn`t tiack cuiient values loi entities that aie maikeu loi ueletion.
Il you tiy to access such values, Entity Fiamewoik will thiow an exception. Let`s auu
some conuitional logic in PrintChangeTrackingInfo to account loi these iestiictions.
Replace the coue that piints out cuiient, oiiginal, anu uataLase values with the coue
in Example 5-+.
Exanp|c 5-1. Avoiding trying to acccss inva|id va|ucs
if (entry.State != EntityState.Deleted)
{
Console.WriteLine("\nCurrent Values:");
PrintPropertyValues(entry.CurrentValues);
}
if (entry.State != EntityState.Added)
{
Console.WriteLine("\nOriginal Values:");
116 | Chapter 5:Change Tracker API
PrintPropertyValues(entry.OriginalValues);
Console.WriteLine("\nDatabase Values:");
PrintPropertyValues(entry.GetDatabaseValues());
}
The upuateu coue now checks the State ol the entiy anu skips piinting out cuiient
values loi entities that aie maikeu loi ueletion. The coue also skips piinting oiiginal
anu uataLase values loi newly auueu entities. Let`s also upuate the PrintLodgingInfo
methou so that we can see these changes in action (Example 5-5).
Exanp|c 5-5. Modijicd PrintLodging|njo ncthod
private static void PrintLodgingInfo()
{
using (var context = new BreakAwayContext())
{
var hotel = (from d in context.Lodgings
where d.Name == "Grand Hotel"
select d).Single();
PrintChangeTrackingInfo(context, hotel);
var davesDump = (from d in context.Lodgings
where d.Name == "Dave's Dump"
select d).Single();
context.Lodgings.Remove(davesDump);
PrintChangeTrackingInfo(context, davesDump);
var newMotel = new Lodging { Name = "New Motel" };
context.Lodgings.Add(newMotel);
PrintChangeTrackingInfo(context, newMotel);
}
}
The upuateu coue now also locates Dave`s Dump anu maiks it loi ueletion. The coue
also auus New Motel to the context. PrintChangeTrackingInfo is calleu loi Loth ol these
entities. Il you iun the application again, you will see that the ielevant change tiacking
inloimation is successlully uisplayeu loi all thiee locations.
The PrintChangeTrackingInfo methou will cuiiently only woik loi Lodgings, Lecause
the entity paiametei is stiongly typeu. But most ol the coue in the methou is not specilic
to the Lodging type. Let`s change the PrintChangeTrackingInfo methou to accept any
entity type Ly changing the entity paiametei to Le typeu as object iathei than Lodging:
private static void PrintChangeTrackingInfo(
BreakAwayContext context,
object entity)
Working with Current, Original, and Database Values | 117
Since the entity may not Le a Lodging, the compilei now selects the nongeneiic Entry
methou, which ietuins the nongeneiic DbEntityEntry. Because we no longei know what
type the entity is, we can`t Le suie that theie is a Name piopeity to piintin lact, the
compilei will give us an eiioi il we tiy to. Remove the line that piinteu out the name
ol the Lodging anu ieplace it with a line that piints out the type ol the entity insteau:
Console.WriteLine("Type: {0}", entry.Entity.GetType());
Go aheau anu iun the application. You will see that the upuateu coue continues to
successlully uisplay change tiacking inloimation.
So lai, oui examples have useu the indcxcr syntax to get the value loi a piopeity out ol
DbPropertyValues. The inuexei syntax is wheie you useu sguaie Liaces on an oLject to
specily the key/inuex ol the value you want to ietiieve (in othei woius, entry.Current
Values[Name]). Because theie is no way to know what type will Le ietuineu liom each
piopeity, the ietuin type ol the inuexei on DbPropertyValues is object. Theie may Le
times wheie you uo know what type the value will Le. Rathei than casting the ietuin
value to the ieguiieu type, you can use the GetValue<TValue> methou to specily the type
ol the value. Foi example, you may want to linu the oiiginal value that was assigneu
to the Name piopeity ol a Lodging when it was ietiieveu liom the uataLase. Auu the
PrintOriginalName methou shown in Example 5-6.
Exanp|c 5-. Using Gct\a|uc<T\a|uc> to gct a strong|y typcd origina| va|uc
private static void PrintOriginalName()
{
using (var context = new BreakAwayContext())
{
var hotel = (from d in context.Lodgings
where d.Name == "Grand Hotel"
select d).Single();
hotel.Name = "Super Grand Hotel";
string originalName = context.Entry(hotel)
.OriginalValues
.GetValue<string>("Name");
Console.WriteLine("Current Name: {0}", hotel.Name);
Console.WriteLine("Original Name: {0}", originalName);
}
}
The coue ietiieves a Lodging liom the uataLase anu changes its Name piopeity. The coue
then looks up the oiiginal value loi the Name piopeity using the GetValue methou on
the OriginalValues loi the entity. Because we know that Name is a string piopeity, the
coue specilies string as the TValue when calling GetValues. The oiiginal value is
ietuineu as a string anu the cuiient anu oiiginal value loi the Name piopeity aie then
piinteu to the console.
118 | Chapter 5:Change Tracker API
Working with DbPropertyValues for Complex Types
Let`s look at how you can woik with DbPropertyValues when you have a piopeity on
youi entity that uses a complex type. RememLei that complex types allow you to gioup
multiple scalai values into a class. A piopeity that ieleiences a complex type is known
as a conp|cx propcrty. Foi example, the BAGA mouel uses an Address complex type to
gioup auuiess ielateu piopeities (Example 5-7).
Exanp|c 5-7. Thc cxisting Addrcss c|asscs
[ComplexType]
public class Address
{
public int AddressId { get; set; }
[MaxLength(150)]
[Column("StreetAddress")]
public string StreetAddress { get; set; }
[Column("City")]
public string City { get; set; }
[Column("State")]
public string State { get; set; }
[Column("ZipCode")]
public string ZipCode { get; set; }
}
Coue Fiist convention iecognizes complex types when the type has no
key piopeity. Since Address has a piopeity that Coue Fiist will iecognize
as a key, AddressId, anu theieloie will inlei this to Le an entity type, the
class is explicitly maikeu as a ComplexType.
This complex type is then useu Ly the Address piopeity in the Person class (Exam-
ple 5-S). Person.Address is theieloie a complex piopeity. Note that PersonInfo is also
a complex type.
Exanp|c 5-8. Thc cxisting Pcrson C|ass
[Table("People")]
public class Person
{
public Person()
{
Address = new Address();
Info = new PersonalInfo
{
Weight = new Measurement(),
Height = new Measurement()
};
}
public int PersonId { get; set; }
[ConcurrencyCheck]
Working with Current, Original, and Database Values | 119
public int SocialSecurityNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
public PersonalInfo Info { get; set; }
public List<Lodging> PrimaryContactFor { get; set; }
public List<Lodging> SecondaryContactFor { get; set; }
[Required]
public PersonPhoto Photo { get; set; }
public List<Reservation> Reservations { get; set; }
}
To uemonstiate how DbPropertyValues hanules complex types, let`s cieate a new Per
son anu pass it to oui PrintChangeTrackingInfo methou. Auu the PrintPersonInfo
methou shown in Example 5-9.
Exanp|c 5-9. Printing changc trac|ing injornation jor an cntity with a conp|cx propcrty
private static void PrintPersonInfo()
{
using (var context = new BreakAwayContext())
{
var person = new Person
{
FirstName = "John",
LastName = "Doe",
Address = new Address { State = "VT" }
};
context.People.Add(person);
PrintChangeTrackingInfo(context, person);
}
}
Vhen we get the value loi a complex piopeity liom DbPropertyValues, it`s going to
ietuin anothei DbPropertyValues that contains the values liom the complex type. Let`s
upuate the PrintPropertyValues helpei methou to account loi this (Example 5-10).
Exanp|c 5-10. PrintPropcrty\a|ucs updatcd to account jor conp|cx propcrtics
private static void PrintPropertyValues(
DbPropertyValues values,
int indent = 1)
{
foreach (var propertyName in values.PropertyNames)
{
var value = values[propertyName];
if (value is DbPropertyValues)
{
Console.WriteLine(
"{0}- Complex Property: {1}",
string.Empty.PadLeft(indent),
120 | Chapter 5:Change Tracker API
propertyName);
PrintPropertyValues(
(DbPropertyValues)value,
indent + 1);
}
else
{
Console.WriteLine(
"{0}- {1}: {2}",
string.Empty.PadLeft(indent),
propertyName,
values[propertyName]);
}
}
}
Foi each piopeity Leing piinteu, the coue now checks il the value is a DbPropertyVal
ues. Il it is, the coue piints out the name ol the complex piopeity anu then iecuisively
calls PrintPropertyValues with the values loi the complex type. PrintPropertyValues
also allows an inuent level to Le supplieu, which is useu to inuent the values ol a com-
plex piopeity. Il an inuent is not supplieu, a uelault inuent ol 1 is useu. Upuate the
Main methou to call PrintPersonInfo anu iun the application. The console will uisplay
the lollowing output:
Type: Model.Person
State: Added
Current Values:
- PersonId: 0
- SocialSecurityNumber: 0
- FirstName: John
- LastName: Doe
- Complex Property: Address
- AddressId: 0
- StreetAddress:
- City:
- State: VT
- ZipCode:
- Complex Property: Info
- Complex Property: Weight
- Reading: 0
- Units:
- Complex Property: Height
- Reading: 0
- Units:
- DietryRestrictions:
The console output is uisplaying the piopeity values containeu in the Address piopeity.
You`ll also notice that the coue woiks loi nesteu complex types. Person.Info is a com-
plex piopeity that ieleiences the PersonInfo complex type. PersonInfo also has complex
piopeities loi a Person`s Height anu Weight. Fiom the piintout you can see that the
Working with Current, Original, and Database Values | 121
DbPropertyValues loi the Inlo complex piopeity ietuineu anothei DbPropertyValues loi
its Weight anu Height piopeities.
Copying the Values from DbPropertyValues into an Entity
Having a single oLject, like DbPropertyValues, that iepiesents a set ol values is hanuy.
Howevei, we usually want to wiite application logic in teims ol oui uomain classes,
iathei than a type such as DbPropertyValues. Foi example, we might have a methou
that uelines how we uisplay a Destination loi the usei ol oui application to see. Ve
can pass any instance ol a Destination into the methou to piint out its cuiient values,
Lut it woulu Le goou il we coulu use that same methou to piint out the oiiginal anu
uataLase values as well. Auu the PrintDestination methou shown in Example 5-11.
Exanp|c 5-11. Mcthod to print injornation about a Dcstination
private static void PrintDestination(Destination destination)
{
Console.WriteLine("-- {0}, {1} --",
destination.Name,
destination.Country);
Console.WriteLine(destination.Description);
if (destination.TravelWarnings != null)
{
Console.WriteLine("WARNINGS!: {0}", destination.TravelWarnings);
}
}
Il you want to uisplay the cuiient values loi a Destination, you can pass the actual
instance to the methou. But theie may Le scenaiios wheie you want to uisplay the
oiiginal values letcheu liom the uataLase oi peihaps the cuiient uataLase values to the
enu usei. One such scenaiio is iesolving concuiiency conllicts uuiing a save. Ve`ll look
at that paiticulai scenaiio in moie uetail latei in this chaptei.
DbPropertyValues incluues a ToObject methou that will copy the values into a new in-
stance ol the entity without oveiwiiting the existing instance as you woulu with a gueiy
to the uataLase. To see how this woiks, auu the TestPrintDestination methou shown
in Example 5-12.
Exanp|c 5-12. Gctting an cntity rcprcscnting thc va|ucs in thc databasc
private static void TestPrintDestination()
{
using (var context = new BreakAwayContext())
{
var reef = (from d in context.Destinations
where d.Name == "Great Barrier Reef"
select d).Single();
reef.TravelWarnings = "Watch out for sharks!";
122 | Chapter 5:Change Tracker API
Console.WriteLine("Current Values");
PrintDestination(reef);
Console.WriteLine("\nDatabase Values");
DbPropertyValues dbValues = context.Entry(reef)
.GetDatabaseValues();
PrintDestination((Destination)dbValues.ToObject());
}
}
The coue ietiieves the Gieat Baiiiei Reel Destination liom the uataLase anu changes
its TravelWarnings piopeity. Then it passes the Destination to the PrintDestination
methou to piint out the cuiient values. Next, it gets the values liom the uataLase anu
uses ToObject to constiuct a Destination that contains the values liom the uataLase.
This new Destination is then passeu to PrintDestination to piint the uataLase values.
Upuate the Main methou to call TestPrintDestination anu iun the application:
Current Values
-- Great Barrier Reef, Australia --
Beautiful coral reef.
WARNINGS!: Watch out for sharks!
Database Values
-- Great Barrier Reef, Australia --
Beautiful coral reef
The cuiient anu uataLase values aie piinteu to the scieen anu you can see that the
upuateu TravelWarnings piopeity is piinteu out in the cuiient values. The seconu in-
stance ol Destination, which was cieateu Ly calling ToObject, is not attacheu to the
context. Any changes to this seconu instance will not Le peisisteu uuiing SaveChanges.
ToObject will only clone the values liom scalai piopeities; all navigation
piopeities on the entity will Le lelt unassigneu. This makes ToObject
uselul loi cloning a single oLject, Lut it will not clone an entiie oLject
giaph loi you.
Changing Values in a DbPropertyValues
DbPropertyValues isn`t a ieau-only type. You can also use it to upuate values that aie
stoieu in an instance. Vhen you set values using CurrentValues oi OriginalValues, this
will also upuate the cuiient anu oiiginal values in the change tiackei. Auuitionally,
upuating the CurrentValues will change the values that aie stoieu in the piopeities ol
youi entity instance.
In Recoiuing Oiiginal Values on page 102, you saw how the OriginalValues coulu
Le inuiviuually upuateu. As each value was upuateu, the Change Tiackei woikeu out
Working with Current, Original, and Database Values | 123
which piopeities hau Leen mouilieu. Let`s take a look at setting the cuiient values. Auu
the ChangeCurrentValue methou shown in Example 5-13.
Exanp|c 5-13. Changing a currcnt va|uc via thc Changc Trac|cr AP|
private static void ChangeCurrentValue()
{
using (var context = new BreakAwayContext())
{
var hotel = (from d in context.Lodgings
where d.Name == "Grand Hotel"
select d).Single();
context.Entry(hotel)
.CurrentValues["Name"] = "Hotel Pretentious";
Console.WriteLine("Property Value: {0}", hotel.Name);
}
}
The coue loaus the Gianu Hotel Lodging liom the uataLase. The coue then gets the
CurrentValues loi the hotel instance anu mouilies the value stoieu loi the Name piopeity.
Finally, the coue wiites out the value stoieu in the Name piopeity on the entity. Upuate
the Main methou to call ChangeCurrentValue anu iun the application, which will iesult
in the lollowing output:
Property Value: Hotel Pretentious
Ve see liom the output that upuating the value ol a piopeity in the CurrentValues has
also upuateu the value stoieu in the piopeity ol the entity.
Back in Voiking with Change Tiacking on page 59, you leaineu that POCO entities
ieguiie a call to DetectChanges to scan loi changes in the piopeities ol the entity. You
also leaineu that DbContext takes caie ol calling DetectChanges loi you, Lut that you
can uisaLle this Lehavioi il you want to contiol when DetectChanges is calleu.
RememLei that in most cases it is Lest to let DbContext automatically
call DetectChanges loi you. Moie inloimation on manually calling
DetectChanges anu the use ol changc trac|ing proxics is availaLle in
Chaptei +.
Il you make changes using the Change Tiackei API, theie is no neeu loi DetectCh
anges to Le calleu, Lecause the Change Tiackei is awaie ol the change Leing maue. To
see this in action, upuate the ChangeCurrentValue methou, as shown in Example 5-1+.
Exanp|c 5-11. Updating via thc Changc Trac|cr AP| rcnovcs thc nccd jor DctcctChangcs
private static void ChangeCurrentValue()
{
using (var context = new BreakAwayContext())
{
124 | Chapter 5:Change Tracker API
context.Configuration.AutoDetectChangesEnabled = false;
var hotel = (from d in context.Lodgings
where d.Name == "Grand Hotel"
select d).Single();
context.Entry(hotel)
.CurrentValues["Name"] = "Hotel Pretentious";
Console.WriteLine("Property Value: {0}", hotel.Name);
Console.WriteLine("State: {0}", context.Entry(hotel).State);
}
}
The upuateu coue now uisaLles automatic calling ol DetectChanges. The coue also
piints out the state ol the hotel entity, as iecoiueu Ly the Change Tiackei, altei the
cuiient value loi Name has Leen upuateu. Go aheau anu iun the application again:
Property Value: Hotel Pretentious
State: Modified
Il we hau upuateu the Name piopeity on the entity, we woulu expect the state to Le
Unchanged, since a call to DetectChanges woulu Le ieguiieu to uiscovei the upuateu
piopeity. Howevei, Lecause the Name piopeity was upuateu using the Change Tiackei
API, the state is coiiectly iecoiueu as Modified without calling DetectChanges.
Theie may Le times when you want to have an euitaLle copy ol the cuiient oi oiiginal
values Lut you uon`t want changes to Le iecoiueu in the Change Tiackei. You`ll see
one such scenaiio when we look at iesolving concuiiency conllicts latei in this chaptei.
The Clone methou will ietuin a copy ol any DbPropertyValues instance. Be awaie that
when you clone cuiient oi oiiginal values, the iesulting copy will not Le hookeu up to
the change tiackei. Auu the CloneCurrentValues methou shown in Example 5-15 to see
how cloning woiks.
Exanp|c 5-15. C|oning currcnt va|ucs
private static void CloneCurrentValues()
{
using (var context = new BreakAwayContext())
{
var hotel = (from d in context.Lodgings
where d.Name == "Grand Hotel"
select d).Single();
var values = context.Entry(hotel).CurrentValues.Clone();
values["Name"] = "Simple Hotel";
Console.WriteLine("Property Value: {0}", hotel.Name);
Console.WriteLine("State: {0}", context.Entry(hotel).State);
}
}
Working with Current, Original, and Database Values | 125
The coue loaus the Gianu Hotel Lodging liom the uataLase anu then clones its cuiient
values. The value stoieu loi the Name piopeity in the cloneu values is upuateu, anu then
the value ol the Name piopeity in the entity is wiitten out. Upuate the Main methou to
call CloneCurrentValues anu iun the application to see the lollowing output in the con-
sole:
Property Value: Grand Hotel
State: Unchanged
As expecteu, upuating the cloneu values has no impact on the values oi the state ol the
entity they weie cloneu liom.
Using the SetValues method
In Chaptei +, you leaineu that you can copy the contents ol one DbPropertyValues into
anothei using the SetValues methou. Foi example, you may want useis ol a client
application with access to the change tiackei to Le aLle to ioll Lack changes they`ve
maue to an entity. The easiest way to uo this is to copy the oiiginal values (when the
entity was ietiieveu liom the uataLase) Lack into the cuiient values. Auu the UndoE
dits methou shown in Example 5-16.
Exanp|c 5-1. Copying origina| va|ucs bac| into currcnt va|ucs
private static void UndoEdits()
{
using (var context = new BreakAwayContext())
{
var canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
select d).Single();
canyon.Name = "Bigger & Better Canyon";
var entry = context.Entry(canyon);
entry.CurrentValues.SetValues(entry.OriginalValues);
entry.State = EntityState.Unchanged;
Console.WriteLine("Name: {0}", canyon.Name);
}
}
The coue ietiieves the Gianu Canyon Destination liom the uataLase anu changes its
Name. The coue then unuoes this euit Ly locating the entiy anu copying the oiiginal
values Lack into the cuiient values. Entity Fiamewoik isn`t smait enough to uetect that
these new values match the oiiginal values, so the coue also manually swaps the state
Lack to Unchanged. Finally, the name piopeity is piinteu out to veiily that the changes
weie ieveiteu. Upuate the Main methou to call UndoEdits anu iun the application. As
expecteu, the changes to the Name piopeity aie ieveiteu anu uisplayeu in the console
like this:
Name: Grand Canyon
126 | Chapter 5:Change Tracker API
SetValues uoesn`t just accept DbPropertyValues, Lut can accept any oL-
ject. SetValues will attempt to oveiwiite the piopeity values with the
values in the oLject that`s Leen passeu in. This is uone Ly matching the
names ol the oLject`s piopeities with the names ol the DbPropertyVal
ues instance. Il a piopeity with the same name is lounu, the value is
copieu. Il the piopeity on the oLject is not ol the same type as the value
stoieu in the DbPropertyValues, an InvalidOperationException is
thiown. Any piopeities in the oLject that uon`t match the name ol a
value alieauy stoieu in the DbPropertyValues aie ignoieu.
Many applications allow you to entei a new iecoiu Ly cloning an existing iecoiu. Let`s
see how to use SetValues to accomplish this task. You might Le a lan ol Dave, ol Dave`s
Dump lame, anu want to cieate a new Dave`s Campsite Lodging using Dave`s Dump
as a staiting point. Auu the CreateDavesCampsite methou shown in Example 5-17.
Exanp|c 5-17. Copying onc cntity into anothcr
private static void CreateDavesCampsite()
{
using (var context = new BreakAwayContext())
{
var davesDump = (from d in context.Lodgings
where d.Name == "Dave's Dump"
select d).Single();
var clone = new Lodging();
context.Lodgings.Add(clone);
context.Entry(clone)
.CurrentValues
.SetValues(davesDump);
clone.Name = "Dave's Camp";
context.SaveChanges();
Console.WriteLine("Name: {0}",
clone.Name);
Console.WriteLine("Miles: {0}",
clone.MilesFromNearestAirport);
Console.WriteLine("Contact Id: {0}",
clone.PrimaryContactId);
}
}
The coue ietiieves Dave`s Dump liom the uataLase. Then it cieates a new Lodging loi
Dave`s Campsite anu auus it to the context. The cuiient values loi the new Campsite
aie then copieu liom Dave`s Dump, using the SetValues methou. The coue then ovei-
wiites the name, since we uon`t want this new Lodging to have the same name, anu
saves to the uataLase. Finally, some ol the piopeities ol the new Lodging aie wiitten
Working with Current, Original, and Database Values | 127
out to the console. Upuate the Main methou to call CreateDavesCampsite anu iun the
application:
Name: Dave's Camp
Miles: 32.65
Contact Id: 1
As expecteu, the uisplayeu values aie the same as Dave`s uump, except loi the Name
piopeity that we oveiwiote.
Working with Individual Properties
DbPropertyValues is a gieat way to woik with complete sets ol values, Lut theie may Le
times when you just want to woik with the change tiacking inloimation loi one piop-
eity. You can ol couise access the values ol a single piopeity using DbPropertyValues,
Lut that uses stiing-Laseu names loi the piopeity. Iueally you shoulu Le using stiongly
typeu lamLua expiessions to iuentily the piopeity, so that you get compile-time check-
ing, IntelliSense, anu ielactoiing suppoit.
You can use the Property, Complex, Reference, anu Collection methous on an entiy to
get access to the change tiacking inloimation anu opeiations loi an inuiviuual piopeity:
The Property methou is useu loi scalai anu complex piopeities.
The Complex methou is useu to get auuitional opeiations that aie specilic to com-
plex piopeities.
The Reference anu Collection methous aie useu loi navigation piopeities.
Theie is also a Member methou, which can Le useu loi any type ol piopeity. The
Member methou is not stiongly typeu anu only pioviues access to inloimation that
is common to all piopeities.
Working with Scalar Properties
Let`s stait with the Property methou. The Property methou allows you to ieau anu
wiite the oiiginal anu cuiient value. It also lets you know whethei an inuiviuual piop-
eity is maikeu as Modified, something that isn`t possiLle with DbPropertyValues. The
WorkingWithPropertyMethod methou shown in Example 5-1S will allow you to Legin
exploiing the Property methou.
Exanp|c 5-18. Acccssing changc trac|ing injornation jor a propcrty
private static void WorkingWithPropertyMethod()
{
using (var context = new BreakAwayContext())
{
var davesDump = (from d in context.Lodgings
where d.Name == "Dave's Dump"
select d).Single();
128 | Chapter 5:Change Tracker API
var entry = context.Entry(davesDump);
entry.Property(d => d.Name).CurrentValue =
"Dave's Bargain Bungalows";
Console.WriteLine(
"Current Value: {0}",
entry.Property(d => d.Name).CurrentValue);
Console.WriteLine(
"Original Value: {0}",
entry.Property(d => d.Name).OriginalValue);
Console.WriteLine(
"Modified?: {0}",
entry.Property(d => d.Name).IsModified);
}
}
The coue ietiieves Dave`s Dump liom the uataLase anu locates the iepiesentative entiy
liom the context. It then uses the Property methou to change the cuiient value loi the
Name piopeity. Then the coue piints out the cuiient anu oiiginal values plus the IsMo
dified llag. The IsModified llag tells us il the piopeity is maikeu as Modified anu will
Le upuateu when SaveChanges is calleu. You can upuate the Main methou to call Work
ingWithPropertyMethod anu iun the application to see these iesults in the console:
Current Value: Dave's Bargain Bungalows
Original Value: Dave's Dump
Modified?: True
The cuiient anu oiiginal values aie uisplayeu as expecteu, since we changeu the value
ol the Name piopeity. You can see that the piopeity is also maikeu as mouilieu. Theie
is also a weakly typeu oveiloau ol Property that accepts a stiing piopeity name iathei
than a lamLua expiession. In lact, all the methous you will see in this section have a
stiing-Laseu oveiloau.
The stiongly typeu lamLua oveiloaus aie iecommenueu Lecause they give you a com-
pile-time check, Lut the stiing-Laseu oveiloaus can Le uselul when wiiting geneializeu
coue. Foi example, you might want to linu out which piopeities aie cuiiently maikeu
as Modified. You can get the names ol all piopeities using CurrentValues anu then check
il they aie mouilieu using the Property methou. The FindModifiedProperties methou
shown in Example 5-19 uemonstiates this.
Exanp|c 5-19. Iinding thc nodijicd propcrtics oj an cntity
private static void FindModifiedProperties()
{
using (var context = new BreakAwayContext())
{
var canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
select d).Single();
Working with Individual Properties | 129
canyon.Name = "Super-Size Canyon";
canyon.TravelWarnings = "Bigger than your brain can handle!!!";
var entry = context.Entry(canyon);
var propertyNames = entry.CurrentValues.PropertyNames;
IEnumerable<string> modifiedProperties = from name in propertyNames
where entry.Property(name).IsModified
select name;
foreach (var propertyName in modifiedProperties)
{
Console.WriteLine(propertyName);
}
}
}
The coue ietiieves the Gianu Canyon Destination liom the uataLase anu changes a
couple ol piopeities. The coue then locates the entiy loi the Gianu Canyon anu gets a
list ol all piopeity names using CurrentValues. Then a LINQ gueiy is useu to linu which
ol those piopeity names aie maikeu as mouilieu. The where section ol the LINQ gueiy
uses the stiing-Laseu oveiloau ol Property to get the change tiacking inloimation loi
the piopeity. Finally, the mouilieu piopeities aie wiitten out to the console. Vhen you
iun FindModifiedProperties, the names ol the two euiteu piopeities aie wiitten out to
the console:
Name
TravelWarnings
The Property methou also gives you access to the name ol the piopeity anu the change
tiackei entiy loi the entity containing the piopeity. This inloimation is pioviueu in the
Name anu EntityEntry piopeities (Figuie 5-1).
Iigurc 5-1. EntityEntry and Nanc jor thc Travc|Warnings propcrty
In the coue you`ve seen so lai, we`ve always known this inloimation Lecause we staiteu
with the change tiacking entiy anu then the piopeity name to linu the inloimation loi
130 | Chapter 5:Change Tracker API
a piopeity. In Chapteis 6 anu 7 you will see how the Name anu EntityEntry piopeities
aie uselul in Valiuation scenaiios.
Working with Complex Properties
Vhen woiking with complex piopeities, you use the ComplexProperty methou to get
access to change tiacking inloimation anu opeiations. The same opeiations that you
just leaineu aLout loi scalai piopeities aie all availaLle loi complex piopeities. You
can also use the Property methou to uiill into inuiviuual scalai piopeities on the com-
plex type. The WorkingWithComplexMethod shown in Example 5-20 uemonstiates intei-
acting with the piopeities ol the Address complex piopeity in a Person instance.
Exanp|c 5-20. Acccssing changc trac|ing injornation jor a conp|cx propcrty.
private static void WorkingWithComplexMethod()
{
using (var context = new BreakAwayContext())
{
var julie = (from p in context.People
where p.FirstName == "Julie"
select p).Single();
var entry = context.Entry(julie);
entry.ComplexProperty(p => p.Address)
.Property(a => a.State)
.CurrentValue = "VT";
Console.WriteLine(
"Address.State Modified?: {0}",
entry.ComplexProperty(p => p.Address)
.Property(a => a.State)
.IsModified);
Console.WriteLine(
"Address Modified?: {0}",
entry.ComplexProperty(p => p.Address).IsModified);
Console.WriteLine(
"Info.Height.Units Modified?: {0}",
entry.ComplexProperty(p => p.Info)
.ComplexProperty(i => i.Height)
.Property(h => h.Units)
.IsModified);
}
}
The coue loaus uata loi ]ulie liom the uataLase anu locates the change tiacking entiy
liom the context. Next it uiills into the Address complex piopeity anu then into the
scalai State piopeity within Address. The coue changes the cuiient value assigneu to
Address.State anu then piints out the IsModified llag loi Address.State anu loi the
Working with Individual Properties | 131
complex Address piopeity. Finally, the coue uiills into a nesteu complex piopeity to
check the IsModified llag loi Info.Height.Units. You can see that ComplexProperty calls
can Le chaineu togethei to uiill into a complex piopeity that is uelineu in anothei
complex piopeity. Following aie the console iesults altei iunning the WorkingWithCom
plexMethod:
Address.State Modified?: True
Address Modified?: True
Info.Height.Units Modified?: False
An alteinative syntax to access the change tiacking inloimation loi a complex piopeity
is to specily the lull path to the piopeity in a single Property call. Foi example, you
coulu also change the cuiient value ol Address.State using the lollowing coue:
entry.Property(p => p.Address.State).CurrentValue = "VT";
You can also specily the lull path il you aie using the stiing-Laseu oveiloau ol Property:
entry.Property("Address.State").CurrentValue = "VT";
Vhen woiking with complex piopeities, Entity Fiamewoik tiacks that state loi the
complex type, Lut not loi its inuiviuual piopeities. Il you check the state ol any piopeity
within the complex type (loi example, the City piopeity ol Address), Entity Fiamewoik
will ietuin the state ol the complex type (Address). Altei changing the Address.State
piopeity, eveiy piopeity ol Address will Le maikeu as mouilieu.
So lai in this section, we have always mouilieu the scalai values ol an existing complex
type instance. You can also assign a new complex type instance to a complex piopeity.
In Example 5-20, insteau ol euiting the State piopeity ol the existing Address instance,
we coulu have ieplaceu it with a new instance:
entry.ComplexProperty(p => p.Address)
.CurrentValue = new Address { State = "VT" };
Replacing the value assigneu to a complex piopeity with a new instance will maik the
entiie complex piopeity as mouilieu.
You may have noticeu in Figuie 5-1 that a ParentProperty is availaLle
altei calling Property oi ComplexProperty. Foi piopeities that aie uelineu
uiiectly on an entity, this will always ietuin null. Foi piopeities that aie
uelineu within a complex piopeity, ParentProperty will ietuin the
change tiacking inloimation loi the paient complex piopeity. Foi ex-
ample, il you aie looking at the inloimation loi the City piopeity insiue
Address, ParentProperty will give you the inloimation loi Address. In
the examples in this chaptei, we always know the paient piopeity Le-
cause we staiteu with the entity, then uiilleu into the complex piopeity,
lolloweu Ly its suLpiopeities.
132 | Chapter 5:Change Tracker API
Working with Navigation Properties
Now it`s time to look at how to access the change tiacking inloimation anu opeiations
associateu with a navigation piopeity. Insteau ol the Piopeity methou, you use the
Reference anu Collection methous to get to navigation piopeities:
Reference is useu when the navigation piopeity is just a ieleience to a single entity
(loi example, Lodging.Destination).
Collection is useu when the navigation piopeity is a collection (loi example, Des
tination.Lodgings).
These methous give you the aLility to uo seveial things:
1. Reau anu wiite the cuiient value assigneu to the navigation piopeity
2. Loau the ielateu uata liom the uataLase
3. Get a gueiy iepiesenting the contents ol the navigation piopeity
In Explicit Loauing on page 36, you saw how the Load methou can Le useu to loau
the contents ol a navigation piopeity liom the uataLase. You also leaineu that the
IsLoaded llag can Le useu to ueteimine il the entiie contents ol a navigation piopeity
(loi example, Destination.Trips) have alieauy Leen loaueu. In Chaptei 2 you also saw
how to use the Query methou to iun a LINQ gueiy against the contents ol the navigation
piopeity. This was in Queiying Contents ol a Collection Navigation Piop-
eity on page 39.
Modifying the value of a navigation property
The Reference methou gives you access to change tiacking inloimation anu opeiations
loi a navigation piopeity. One piece ol inloimation that is availaLle is the value cui-
iently assigneu to the navigation piopeity. This is accesseu via the CurrentValue piop-
eity. Il the navigation piopeity hasn`t Leen loaueu liom the uataLase, CurrentValue will
ietuin null. You can also set the CurrentValue piopeity to change the entity assigneu
to the navigation piopeity, theieloie changing the ielationship. Auu the coue loi Work
ingWithReferenceMethod, shown in Example 5-21.
Exanp|c 5-21. Changc trac|ing injornation jor a rcjcrcncc navigation propcrty
private static void WorkingWithReferenceMethod()
{
using (var context = new BreakAwayContext())
{
var davesDump = (from d in context.Lodgings
where d.Name == "Dave's Dump"
select d).Single();
var entry = context.Entry(davesDump);
entry.Reference(l => l.Destination)
.Load();
Working with Individual Properties | 133
var canyon = davesDump.Destination;
Console.WriteLine(
"Current Value After Load: {0}",
entry.Reference(d => d.Destination)
.CurrentValue
.Name);
var reef = (from d in context.Destinations
where d.Name == "Great Barrier Reef"
select d).Single();
entry.Reference(d => d.Destination)
.CurrentValue = reef;
Console.WriteLine(
"Current Value After Change: {0}",
davesDump.Destination.Name);
}
}
The coue ietiieves Dave`s Dump Lodging liom the uataLase anu locates the change
tiacking entiy liom the context. Then it uiills into the Destination ieleience anu ex-
plicitly loaus the ielateu uata using Reference().Load(). The name ol the Destination
that Dave`s Dump is assigneu is then wiitten out to the console using the Current
Value piopeity. Next, we change CurrentValue Ly assigning the Gieat Baiiiei Reel
Destination. Finally, we`ll wiite out the name ol the Destination that Dave`s Dump is
assigneu again, Lut this time Ly accessing the navigation piopeity on the davesDump
entity itsell.
Upuate the Main methou to call WorkingWithReferenceMethod anu iun the application
to see the lollowing iesults:
Current Value After Load: Grand Canyon
Current Value After Change: Great Barrier Reef
CurrentValue alloweu you to ieau anu wiite the Destination that Dave`s Dump is as-
signeu to. Changing the CurrentValue also upuateu the navigation piopeity on the
davesDump entity.
Modifying navigation properties with the change tracker
Eailiei, when woiking with scalai piopeities, you saw that DetectChanges was not ie-
guiieu when making changes thiough the change tiackei. The same is tiue loi ieleience
navigation piopeities. Change uetection anu ielationship lix-up occui without
DetectChanges Leing calleu. To see this in action, upuate the WorkingWithReferenceMe
thod methou to uisaLle automatic change uetection anu lazy loauing. Auu the lollowing
coue immeuiately Leloie the LINQ gueiy that ietiieves Dave`s Dump liom the
uataLase:
134 | Chapter 5:Change Tracker API
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.LazyLoadingEnabled = false;
Let`s also piint some auuitional inloimation to the console to oLseive how the context
is tiacking the changes. Auu the lollowing coue altei the linal Console.WriteLine call
in the existing methou:
Console.WriteLine(
"State: {0}",
entry.State);
Console.WriteLine(
"Referenced From Current Destination: {0}",
reef.Lodgings.Contains(davesDump));
Console.WriteLine(
"Referenced From Former Destination: {0}",
canyon.Lodgings.Contains(davesDump));
The coue piints out the state ol Dave`s Dump as iecoiueu Ly the change tiackei. Then
it piints out whethei Dave`s Dump is piesent in Lodgings collection on the cuiient
Destination (reef) anu the loimei Destination (canyon). Go aheau anu iun the appli-
cation again, which will piint these iesults in the console:
After Load: Grand Canyon
After CurrentValue Change: Great Barrier Reef
State: Modified
Referenced From Current Destination: True
Referenced From Former Destination: False
The change tiackei is awaie that Dave`s Dump is Modified without calling DetectCh
anges. The change tiackei has also taken caie ol upuating the Destination ieleience on
Dave`s Dump, iemoving Dave`s Dump liom the Lodgings collection on the loimei
Destination, anu auuing it to the new Destination.
Moie inloimation on ielationship lix-up is availaLle in Using De-
tectChanges to Tiiggei Relationship Fix-up on page 63.
Working with collection navigation properties
You`ve seen many leatuies ol woiking with a ieleience navigation using the Refer
ence methou. The same opeiations aie availaLle when using the Collection methou to
inteiact with a collection navigation piopeity. The WorkingWithCollectionMethod
methou, shown in Example 5-22, iuns thiough some ol the same tasks, Lut this time
with a navigation piopeity that points to a collection. Ve`ie using Reservation.Pay
ments as oui collection navigation piopeity iathei than Destination.Lodgings. Back in
Chaptei 3, we set up Destination to use a uynamic change tiacking pioxy so that
changes woulu Le automatically iepoiteu to the change tiackei. But Reservation is not
Working with Individual Properties | 135
set up to use a change tiacking pioxy. This will allow us to exploie how the Collec
tion methou Lehaves with change uetection anu ielationship lix-up.
Exanp|c 5-22. Mcthod to cxp|orc intcracting with a co||cction propcrty
private static void WorkingWithCollectionMethod()
{
using (var context = new BreakAwayContext())
{
var res = (from r in context.Reservations
where r.Trip.Description == "Trip from the database"
select r).Single();
var entry = context.Entry(res);
entry.Collection(r => r.Payments)
.Load();
Console.WriteLine(
"Payments Before Add: {0}",
entry.Collection(r => r.Payments).CurrentValue.Count);
var payment = new Payment { Amount = 245 };
context.Payments.Add(payment);
entry.Collection(r => r.Payments)
.CurrentValue
.Add(payment);
Console.WriteLine(
"Payments After Add: {0}",
entry.Collection(r => r.Payments).CurrentValue.Count);
}
}
The methou loaus a Reservation liom the uataLase anu locates its change tiacking entiy
liom the context. Then it uiills into the Payments piopeity using the Collection methou
anu, just as we uiu with the Reference, uses the Load methou to explicitly loau any
ielateu Payments liom the uataLase. The methou then calls the Collection.Current
Value.Count piopeity to count how many payments aie in the collection anu piints out
the count. Finally, the methou auus a new payment anu piints out the count again.
Upuate the Main methou to call WorkingWithCollectionMethod anu iun the application.
Heie is what you`ll see in the console:
Payments Before Add: 1
Payments After Add: 2
Vith a Collection, you can use the CurrentValue piopeity to ieau anu wiite liom the
ielevant collection navigation piopeity (in this case, Payments). CurrentValue on col-
lection navigation piopeities ietuins the instance ol the collection assigneu to the nav-
igation piopeity. In the case ol Reservation.Payments, that`s the List<Payment> that gets
cieateu in the constiuctoi ol Reservation. Theieloie, auuing oi iemoving liom the
136 | Chapter 5:Change Tracker API
CurrentValue Lehaves the same as auuing oi iemoving liom the navigation piopeity
itsell. This means that unless the entity is a change tiacking pioxy, you`ll neeu DetectCh
anges to get change uetection anu ielationship lix-up to occui. Let`s see how this allects
the iesults ol the WorkingWithCollectionMethod methou Ly auuing a line ol coue to
uisaLle automatic change uetection. Auu the lollowing line ol coue immeuiately Leloie
the LINQ gueiy that ietiieves the Reservation liom the uataLase:
context.Configuration.AutoDetectChangesEnabled = false;
Also auu the lollowing coue altei the linal Console.WriteLine call. This new coue calls
DetectChanges altei auuing the Payment. The value assigneu to the loieign key on the
new Payment is piinteu out to the console on eithei siue ol the DetectChanges call:
Console.WriteLine(
"Foreign Key Before DetectChanges: {0}",
payment.ReservationId);
context.ChangeTracker.DetectChanges();
Console.WriteLine(
"Foreign Key After DetectChanges: {0}",
payment.ReservationId);
Go aheau anu iun the application again. You can see the ellect ol setting AutoDetectCh
angesEnabled to lalse anu the explicit DetectChanges call in the console output:
Count Before Add: 1
Count After Add: 2
Foreign Key Before DetectChanges: 0
Foreign Key After DetectChanges: 1
The auuition ol the Payment to the Payments collection ol the Reservation was not au-
tomatically uetecteu. Relationship lix-up was not tiiggeieu until DetectChanges was
calleu.
Refreshing an Entity from the Database
Thioughout this Look you have seen how to loau uata liom the uataLase anu woik
with it in-memoiy. So lai, you have only loaueu the uata one time, Lut theie may Le
times when you want to ieliesh oi ieloau a given entity. Foi example you may have
hau an entity in memoiy loi a long peiiou ol time anu want to make suie you have the
latest uata Leloie uisplaying it to a usei.
Entity Fiamewoik incluues a Reloau methou on DbEntityEntry that can Le useu to
ieliesh an entity with the latest uata liom the uataLase. The ReloadLodging methou
shown in Example 5-23 uses this methou.
Exanp|c 5-23. Rc|oading an cntity jron thc databasc
private static void ReloadLodging()
{
Refreshing an Entity from the Database | 137
using (var context = new BreakAwayContext())
{
var hotel = (from d in context.Lodgings
where d.Name == "Grand Hotel"
select d).Single();
context.Database.ExecuteSqlCommand(
@"UPDATE dbo.Lodgings
SET Name = 'Le Grand Hotel'
WHERE Name = 'Grand Hotel'");
Console.WriteLine(
"Name Before Reload: {0}",
hotel.Name);
Console.WriteLine(
"State Before Reload: {0}",
context.Entry(hotel).State);
context.Entry(hotel).Reload();
Console.WriteLine(
"Name After Reload: {0}",
hotel.Name);
Console.WriteLine(
"State After Reload: {0}",
context.Entry(hotel).State);
}
}
The methou ietiieves the Gianu Hotel Lodging liom the uataLase anu then, loi the sake
ol uemoing this leatuie, issues a iaw SQL gueiy to upuate its Name in the uataLase. The
coue then calls Reload to ieliesh with the latest uata liom the uataLase. Notice that the
coue piints out the value assigneu to the Name piopeity anu the state ol the entity Leloie
anu altei calling Reload. Upuate the Main methou to call ReloadLodging anu iun the
application to see the ellect. This is the output in the console:
Name Before Reload: Grand Hotel
State Before Reload: Unchanged
Name After Reload: Le Grand Hotel
State After Reload: Unchanged
You can see that the new value loi the Name piopeity was ietiieveu liom the uataLase.
Reload will also oveiwiite any changes you have in memoiy. To see this ellect, upuate
the ReloadLodging methou to euit the Lodging Leloie ieloauing. Auu the lollowing line
ol coue immeuiately altei the LINQ gueiy that populates the hotel vaiiaLle:
hotel.Name = "A New Name";
The coue now mouilies the Name piopeity ol the entity in memoiy Leloie calling
Reload. This will now Le the output ol the methou:
138 | Chapter 5:Change Tracker API
Name Before Reload: A New Name
State Before Reload: Modified
Name After Reload: Le Grande Hotel
State After Reload: Unchanged
Because we euiteu the Name piopeity, the entity state is Modified Leloie the Reload. Altei
the Reload, the entity is now maikeu as Unchanged, Lecause any changes weie oveiwiit-
ten with uata liom the uataLase.
Change Tracking Information and Operations for
Multiple Entities
So lai you have seen how to get access to the DbEntityEntry loi a single entity. Some-
times you might want to get access to entiies loi all entities oi a suLset ol the entiies
tiackeu Ly the context. You can uo this using the DbContext.ChangeTracker.Entries
methou. Theie is a geneiic Entries<TEntity> oveiloau that ietuins a collection ol DbEn
tityEntry<TEntity> iecoius loi all entities that aie ol the type specilieu loi TEntity. The
nongeneiic oveiloau ol Entries uoes not allow you to specily the type, anu it ietuins a
collection ol DbEntityEntry iecoius loi all ol the tiackeu entities.
Ve`ll stait Ly looking at all entiies known Ly the change tiackei using the nongeneiic
oveiloau using the PrintChangeTrackerEntries methou shown in Example 5-2+.
Exanp|c 5-21. |tcrating ovcr a|| cntrics jron thc changc trac|cr
private static void PrintChangeTrackerEntries()
{
using (var context = new BreakAwayContext())
{
var res = (from r in context.Reservations
where r.Trip.Description == "Trip from the database"
select r).Single();
context.Entry(res)
.Collection(r => r.Payments)
.Load();
res.Payments.Add(new Payment { Amount = 245 });
var entries = context.ChangeTracker.Entries();
foreach (var entry in entries)
{
Console.WriteLine(
"Entity Type: {0}",
entry.Entity.GetType());
Console.WriteLine(
" - State: {0}",
entry.State);
}
Change Tracking Information and Operations for Multiple Entities | 139
}
}
The coue ietiieves a Reservation liom the uataLase anu then uses explicit loauing to
Liing its ielateu Payments into memoiy. The coue also cieates a new Payment anu auus
it to the Payments collection ol the Reservation. Then we use the Entries methou to
ietiieve all change tiackeu entiies liom the context. As the coue iteiates ovei the entiies,
it piints out the type ol entity anu its cuiient state. Calling PrintChangeTrackerEn
tries liom the Main methou iesults in this console output:
Entity Type: Model.Payment
- State: Added
Entity Type: Model.Reservation
- State: Unchanged
Entity Type: Model.Payment
- State: Unchanged
The Entries methou ietuins an entiy loi the Reservation anu its existing Payment, as
well as the new Payment we auueu. You`ll notice that the entiies aien`t ietuineu in the
oiuei they Legan Leing tiackeu Ly the context.
You shoulun`t iely on the oiuei that entiies aie ietuineu in, as it may
change Letween veisions ol Entity Fiamewoik.
You can also use LINQ to OLjects to gueiy the iesult ol the Entiies methou. Replace
the line ol coue in PrintChangeTrackerEntries that populates the entries vaiiaLle to
use a LINQ gueiy:
var entries = from e in context.ChangeTracker.Entries()
where e.State == EntityState.Unchanged
select e;
This upuateu coue uses a LINQ gueiy to select only the entiies loi entities that aie
tiackeu in the Unchanged state. Il you iun the application you will see that the inloima-
tion loi the Added Payment is no longei uisplayeu:
Entity Type: Model.Reservation
- State: Unchanged
Entity Type: Model.Payment
- State: Unchanged
Anothei way to liltei is to use the geneiic oveiloau ol Entries to specily which types
you want entiies loi. Change the Entries call in PrintChangeTrackerEntries again along
with the coue, which wiites to the console:
var entries = context.ChangeTracker.Entries<Payment>();
foreach (var entry in entries)
{
Console.WriteLine(
"Amount: {0}",
entry.Entity.Amount);
140 | Chapter 5:Change Tracker API
Console.WriteLine(
" - State: {0}",
entry.State);
}
The call now uses the geneiic oveiloau ol Entries to specily that we aie only inteiesteu
in entiies loi the Payment type. Thanks to the geneiic oveiloau, the Entity piopeity on
the ietuineu entiies is now stiongly typeu as Payment. Ve`ll piint out the payment
Amount insteau ol the type ol the entity. Il you iun the application again, you will see
that only inloimation loi the two Payment entities is piinteu:
Amount: 245
- State: Added
Amount: 150.00
- State: Unchanged
The type that you supply to the geneiic oveiloau ol Entries uoes not neeu to Le a type
that is incluueu in youi mouel. Foi example, in Chaptei +, you saw the geneiic oveiloau
useu to get all entiies loi entities that implementeu a given inteilace:
context.ChangeTracker.Entries<IObjectWithState>()
Using the Change Tracker API in Application Scenarios
Ve`ve coveieu a lot ol lunctionality in this chaptei, so let`s look at a couple ol examples
ol how that lunctionality can Le useu in an application.
You`ll see how you can use the Change Tiackei API to iesolve concuiiency conllicts
anu also to log changes that aie maue uuiing SaveChanges.
Resolving Concurrency Conflicts
A concuiiency conllict occuis when you attempt to upuate a iecoiu in the uataLase Lut
anothei usei has upuateu that same iecoiu since you gueiieu loi it. By uelault, Entity
Fiamewoik will always upuate the piopeities that you have mouilieu iegaiuless ol
whethei oi not theie is a concuiiency conllict. Howevei, you can conliguie youi mouel
so that Entity Fiamewoik will thiow an exception when a concuiiency conllict occuis.
You uo this Ly specilying that a specilic piopeity shoulu Le useu as a concuiiency token.
How to conliguie youi mouel loi optimistic concuiiency is coveieu in
uetail in Progranning Entity Irancwor|, 2c (loi EDMX mouels) anu in
Progranning Entity Irancwor|: Codc Iirst (loi mouels uelineu using
Coue Fiist).
Duiing SaveChanges Entity Fiamewoik will check il the value in the coiiesponuing
uataLase column has Leen upuateu since the iecoiu was Lought into memoiy. A con-
cuiiency exception is thiown il the value in the uataLase has changeu.
Using the Change Tracker API in Application Scenarios | 141
The BAGA mouel incluues two examples ol concuiiency tokens. The SocialSecurity
Number piopeity on Person is maikeu with the ConcurrencyCheck attiiLute. Vhen up-
uating an existing Person, Entity Fiamewoik will check that the SSN allocateu to the
Person when the iecoiu was ietiieveu liom the uataLase iemains the same in the uata-
Lase. Il anothei usei has changeu the SSN, SaveChanges will lail anu a DbUpdateConcur
rencyException will Le thiown. The Trip class incluues a RowVersion piopeity that is
maikeu with the Timestamp attiiLute. Timestamp piopeities aie tieateu the same as
othei concuiiency check piopeities, except the uataLase will automatically geneiate a
new value loi this piopeity whenevei any column in the iecoiu is upuateu. This means
that when saving changes to an existing Trip, a concuiiency exception will Le thiown
il anothei usei has upuateu any piopeities since the Trip was ietiieveu liom the uata-
Lase.
Let`s wiite a methou that will cause a DbUpdateConcurrencyException to Le thiown when
tiying to save a change to an existing Trip. Vhen the exception is thiown, we`ll ask the
enu usei ol oui application to tell us how they want to iesolve the conllict. Go aheau
anu auu the ConcurrencyDemo methou shown in Example 5-25.
Exanp|c 5-25. Causing a concurrcncy cxccption.
private static void ConcurrencyDemo()
{
using (var context = new BreakAwayContext())
{
var trip = (from t in context.Trips.Include(t => t.Destination)
where t.Description == "Trip from the database"
select t).Single();
trip.Description = "Getaway in Vermont";
context.Database.ExecuteSqlCommand(
@"UPDATE dbo.Trips
SET CostUSD = 400
WHERE Description = 'Trip from the database'");
SaveWithConcurrencyResolution(context);
}
}
private static void SaveWithConcurrencyResolution(
BreakAwayContext context)
{
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
ResolveConcurrencyConflicts(ex);
SaveWithConcurrencyResolution(context);
142 | Chapter 5:Change Tracker API
}
}
The example ietiieves an existing Trip liom the uataLase anu changes its Description
piopeity. It then issues a iaw SQL gueiy to upuate the CostUSD column ol the same
Trip in the uataLase. Executing this statement will cause the uataLase to geneiate a new
value loi the RowVersion column in the uataLase. Issuing a iaw SQL statement is not a
iecommenueu piactice anu is just useu to simulate anothei usei changing uata. Next,
the coue calls the SaveWithConcurrencyResolution helpei methou. This helpei methou
calls SaveChanges, which will issue an UPDATE commanu to apply oui changes to the
Trip. As pait ol the upuate piocess, Entity Fiamewoik will check il the RowVersion
column in the uataLase still has the same value as it uiu when we gueiieu loi the
Trip. Because ol change we maue with ExecuteSqlCommand, the RowVersion will have
changeu anu a DbUpdateConcurrencyException will Le thiown. The example coue
catches this exception anu attempts to iesolve the conllict with a custom methou,
ResolveConcurrencyConflict. Once the conllict is iesolveu, the coue makes a iecuisive
call to SaveWithConcurrencyResolution. The iecuisive call is useu to ensuie that we
hanule any luithei concuiiency conllicts that occui altei the liist conllict is iesolveu.
Example 5-26 shows the ResolveConcurrencyConflict methou.
Exanp|c 5-2. Rcso|ving a concurrcncy conj|ict
private static void ResolveConcurrencyConflicts(
DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
Console.WriteLine(
"Concurrency conflict found for {0}",
entry.Entity.GetType());
Console.WriteLine("\nYou are trying to save the following values:");
PrintPropertyValues(entry.CurrentValues);
Console.WriteLine("\nThe values before you started editing were:");
PrintPropertyValues(entry.OriginalValues);
var databaseValues = entry.GetDatabaseValues();
Console.WriteLine("\nAnother user has saved the following values:");
PrintPropertyValues(databaseValues);
Console.Write(
"[S]ave your values, [D]iscard you changes or [M]erge?");
var action = Console.ReadKey().KeyChar.ToString().ToUpper();
switch (action)
{
case "S":
entry.OriginalValues.SetValues(databaseValues);
break;
Using the Change Tracker API in Application Scenarios | 143
case "D":
entry.Reload();
break;
case "M":
var mergedValues = MergeValues(
entry.OriginalValues,
entry.CurrentValues,
databaseValues);
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(mergedValues);
break;
default:
throw new ArgumentException("Invalid option");
}
}
}
Foitunately, the DbUpdateConcurrencyException gives you access to eveiything you neeu
to know aLout the conllict. The exception`s Entries piopeity gives you the DbEntityEn
try loi each ol the entities that hau a concuiiency conllict.
Because Entity Fiamewoik stops at the liist exception, the Entries
piopeity on DbUpdateConcurrencyException will almost always contain
just a single entiy. Il you have a ielationship that uoes not expose a
loieign key piopeity on youi entity, Entity Fiamewoik tieats ielation-
ships as sepaiate liom the entity; these ielationships aie known as in-
dcpcndcnt associations. SaveChanges also tieats the ielationships as sep-
aiate liom the entity. Il a concuiiency conllict occuis when saving the
ielationship, the iesulting exception will incluue the entiy loi the entity
on each enu ol the ielationship. This is yet one moie goou ieason to
always incluue loieign key piopeities in youi entities.
The ResolveConcurrencyConflicts methou iteiates thiough each ol the entiies in the
exception to iesolve the conllict. It lets the usei know what type ol entity the conllict
occuiieu in Ly checking the type ol the entity in the Entity piopeity. Next the usei is
shown the cuiient, oiiginal, anu uataLase values using the PrintPropertyValues methou
you auueu Lack in Example 5-10. The coue then gives the usei thiee options loi ie-
solving the conllict:
Savc your va|ucs
Il the changes that anothei usei has maue uon`t make sense given the changes the
cuiient usei is making, the usei can pioceeu with saving his oi hei values to the
uataLase. This option will oveiwiite any changes that weie maue Ly othei useis,
even il those changes weie to a piopeity that the usei is not tiying to upuate. Foi
144 | Chapter 5:Change Tracker API
example, the uiop to $+00 that the othei usei applieu might not Le applicaLle now
that the Trip is visiting Leautilul Veimont.
Il the usei selects this option, the oiiginal values aie set to the uataLase values. This
upuates the RowVersion piopeity with the new value liom the uataLase, so that the
next save will succeeu. Setting the oiiginal values will also maik any piopeities that
have a uilleient cuiient value as Modified. In oui example, only the Description
piopeity was mouilieu. Howevei, the cuiient value loi CostUSD is still the value
ietiieveu liom the uataLase ($1000), Lut the uataLase value has Leen upuateu
($+00). Theieloie the CostUSD piopeity will get maikeu as Modifiedto Le set Lack
to the value when oiiginally gueiieu ($1000).
Discard your changcs
Il the usei`s changes no longei make sense, the usei can uiscaiu his oi hei changes
anu accept the new values that the othei usei has saveu. Foi example, the usei
might ueciue that given the piice ieuuction the othei usei applieu, it`s Lettei to
leave the Trip Description as it was.
The DbEntityEntry.Reload methou makes this option veiy simple to implement.
Reload will gueiy the uataLase again loi the uataLase values. Il you wanteu to avoiu
this auuitional gueiy, you coulu also set the oiiginal anu cuiient values to the
uataLase values. RememLei that Entity Fiamewoik isn`t smait enough to move
piopeities Lack out ol the Modified state il you change the cuiient anu oiiginal
value to the same thing. Theieloie, you woulu also neeu to set the State piopeity
on the entiy Lack to Unchanged. Calling Reload has one small auvantage; it will
always pick up the veiy latest veision ol the entity at the time the usei ueciues to
uiscaiu his oi hei changes. Il anothei usei has mouilieu the allecteu entity again,
altei we uetecteu the conllict anu gueiieu the uataLase values, then Reload will pick
up this latest set ol changes.
Mcrgc
The usei may ueciue that Loth sets ol changes make sense anu they shoulu Le
meigeu. The usei`s change to the Description may Le completely unielateu to the
uiop in piice. The piice shoulu iemain at $+00 Lut the change to Description
shoulu also Le applieu.
Ve`ll use a custom MergeValues methou, which we aie aLout to auu, to calculate
the coiiect values to save. These meigeu values aie then set to the cuiient values.
The uataLase values aie set to the oiiginal values to ensuie the RowVersion piopeity
has the new value liom the uataLase, so that the next save will succeeu.
The linal piece ol coue to auu is the MergeValues methou that will Le useu when the
usei ueciues to meige his oi hei changes with the changes anothei usei has applieu
(Example 5-27).
Exanp|c 5-27. Thc Mcrgc\a|ucs ncthod
private static DbPropertyValues MergeValues(
DbPropertyValues original,
Using the Change Tracker API in Application Scenarios | 145
DbPropertyValues current,
DbPropertyValues database)
{
var result = original.Clone();
foreach (var propertyName in original.PropertyNames)
{
if (original[propertyName] is DbPropertyValues)
{
var mergedComplexValues = MergeValues(
(DbPropertyValues)original[propertyName],
(DbPropertyValues)current[propertyName],
(DbPropertyValues)database[propertyName]);
((DbPropertyValues)result[propertyName])
.SetValues(mergedComplexValues);
}
else
{
if (!object.Equals(
current[propertyName],
original[propertyName]))
{
result[propertyName] = current[propertyName];
}
else if (!object.Equals(
database[propertyName],
original[propertyName]))
{
result[propertyName] = database[propertyName];
}
}
}
return result;
}
MergeValues Legins Ly using the Clone methou to cieate a set ol values that will stoie
the meigeu iesult. Ve aie cloning liom the oiiginal values, Lut you coulu clone liom
any ol the thiee sets ol values (cuiient, oiiginal, oi uataLase). Note that the coue is
going to assume that the thiee sets ol supplieu values contain exactly the same piop-
eitiesin oui case, we know this is tiue Lecause they come liom the same entity. The
methou then loops thiough each piopeity to peiloim the meige.
Il the piopeity value is a DbPropertyValues, we know it iepiesents a complex type. Foi
complex types, the coue uses a iecuisive call to meige the values in the complex type.
The meigeu values loi the complex type aie then copieu into the iesult using the
SetValues methou.
Foi scalai piopeities, the coue compaies the cuiient value to the oiiginal value to see
il the cuiient usei has euiteu the value. Il the cuiient usei has euiteu the value, the
cuiient value is copieu to the meigeu iesult. Il the cuiient usei hasn`t euiteu the value
Lut anothei usei has changeu it in the uataLase, the uataLase value is copieu to the
146 | Chapter 5:Change Tracker API
iesult. Il noLouy has euiteu the value, all thiee value collections agiee anu the value
that was oiiginally cloneu can Le lelt in the meigeu iesult.
Il you want to test out the coue, upuate the Main methou to call ConcurrencyDemo anu
iun the application.
Logging During Save Changes
The change tiacking inloimation that Entity Fiamewoik maintains is also uselul il you
want to log the changes that a usei is making uuiing SaveChanges. Ve`ll take a look at
an example that wiites the changes to the Console, Lut the technigues coulu Le useu
loi any numLei ol logging oi auuiting solutions.
The logging output coulu get annoying il we leave it on loi eveiy example in this Look,
so we aie going to intiouuce a llag that allows us to tuin it on when we want to use it.
Auu the lollowing piopeity to youi BreakAwayContext class:
public bool LogChangesDuringSave { get; set; }
You`ll neeu some helpei methous to piint out the ieguiieu logging inloimation. Auu
the two methous shown in Example 5-2S to youi BreakAwayContext class. You`ll neeu
to auu a using statement loi the System, System.Data, anu System.Linq namespaces.
Exanp|c 5-28. Hc|pcr ncthods jor |ogging
private void PrintPropertyValues(
DbPropertyValues values,
IEnumerable<string> propertiesToPrint,
int indent = 1)
{
foreach (var propertyName in propertiesToPrint)
{
var value = values[propertyName];
if (value is DbPropertyValues)
{
Console.WriteLine(
"{0}- Complex Property: {1}",
string.Empty.PadLeft(indent),
propertyName);
var complexPropertyValues = (DbPropertyValues)value;
PrintPropertyValues(
complexPropertyValues,
complexPropertyValues.PropertyNames,
indent + 1);
}
else
{
Console.WriteLine(
"{0}- {1}: {2}",
string.Empty.PadLeft(indent),
propertyName,
values[propertyName]);
Using the Change Tracker API in Application Scenarios | 147
}
}
}
private IEnumerable<string> GetKeyPropertyNames(object entity)
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
return objectContext
.ObjectStateManager
.GetObjectStateEntry(entity)
.EntityKey
.EntityKeyValues
.Select(k => k.Key);
}
The PrintPropertyValues methou is almost the same as the PrintPropertyValues you
auueu to the Program class Lack in Example 5-10. The only uilleience is that this methou
accepts the names ol the piopeities that shoulu Le piinteu, iathei than piinting all
piopeities. As the name suggests, the GetKeyPropertyNames methou will give you the
names ol the piopeities that make up the key ol an entity. Theie is no way to get this
inloimation liom the DLContext API, so the coue uses the IObjectContextAdapter to
get the unueilying ObjectContext. You`ll leain moie aLout IObjectContextAdapter in
Chaptei S. The coue gets the key piopeity names Ly getting the EntityKey loi the entity
liom the ObjectStateManager. ObjectStateManager is the ObjectContext eguivalent to
the DbContext.ChangeTracker.
Vith the helpei methous in place, let`s wiite the logging coue. The easiest way to iun
auuitional logic uuiing the save piocess is to oveiiiue the SaveChanges methou on youi
ueiiveu context. The SaveChanges methou on DbContext is virtual (Overrideable in
Visual Basic) loi exactly this puipose. Auu the oveiiiuuen SaveChanges methou shown
in Example 5-29 to youi BreakAwayContext class.
Exanp|c 5-29. Ovcrriding SavcChangcs to pcrjorn |ogging
public override int SaveChanges()
{
if (LogChangesDuringSave)
{
var entries = from e in this.ChangeTracker.Entries()
where e.State != EntityState.Unchanged
select e;
foreach (var entry in entries)
{
switch (entry.State)
{
case EntityState.Added:
Console.WriteLine(
"Adding a {0}",
entry.Entity.GetType());
148 | Chapter 5:Change Tracker API
PrintPropertyValues(
entry.CurrentValues,
entry.CurrentValues.PropertyNames);
break;
case EntityState.Deleted:
Console.WriteLine(
"Deleting a {0}",
entry.Entity.GetType());
PrintPropertyValues(
entry.OriginalValues,
GetKeyPropertyNames(entry.Entity));
break;
case EntityState.Modified:
Console.WriteLine(
"Modifying a {0}",
entry.Entity.GetType());
var modifiedPropertyNames =
from n in entry.CurrentValues.PropertyNames
where entry.Property(n).IsModified
select n;
PrintPropertyValues(
entry.CurrentValues,
GetKeyPropertyNames(entry.Entity)
.Concat(modifiedPropertyNames));
break;
}
}
}
return base.SaveChanges();
}
The coue checks il the LogChangesDuringSave piopeity is set to trueLy uelault the
piopeity is set to false. Il logging is enaLleu, the logging logic is executeu. The coue
then locates the entiies loi all entities that aie going to Le saveuthat`s all entities that
aien`t in the Unchanged state. Foi each ol these entities, the coue iuentilies the change
Leing peiloimeu anu the type ol entity it is Leing peiloimeu on. Then it piints out the
values ol some ol the piopeities ol the entity. The set ol piopeities that gets piinteu
uepenus on the type ol opeiation Leing peiloimeu:
Foi Added entities, the entiie set ol cuiient values that aie going to Le inseiteu aie
piinteu.
Foi Deleted entities, just the key piopeities aie piinteu out, since this is enough
inloimation to iuentily the iecoiu Leing ueleteu.
Foi Modified entities, the key piopeities anu the piopeities that aie Leing upuateu
aie piinteu out.
Using the Change Tracker API in Application Scenarios | 149
Il you want to test the coue out, auu the TestSaveLogging methou shown in
Example 5-30.
Exanp|c 5-30. Mcthod to tcst out |ogging during savc
private static void TestSaveLogging()
{
using (var context = new BreakAwayContext())
{
var canyon = (from d in context.Destinations
where d.Name == "Grand Canyon"
select d).Single();
context.Entry(canyon)
.Collection(d => d.Lodgings)
.Load();
canyon.TravelWarnings = "Take a hat!";
context.Lodgings.Remove(canyon.Lodgings.First());
context.Destinations.Add(new Destination { Name = "Seattle, WA" });
context.LogChangesDuringSave = true;
context.SaveChanges();
}
}
The coue ietiieves the Gianu Canyon Destination anu its ielateu Lodgings liom the
uataLase. The Gianu Canyon Destination is then mouilieu, one ol its Lodgings is
maikeu loi ueletion, anu a new Destination is auueu to the context. The coue then
enaLles the logging you just auueu anu calls SaveChanges. Upuate the Main methou to
call TestSaveLogging anu iun the application to see the log inloimation in the console:
Adding a new Model.Destination
- DestinationId: 0
- Name: Seattle, WA
- Country:
- Description:
- Photo:
- TravelWarnings:
- ClimateInfo:
Modifiying an existing
System.Data.Entity.DynamicProxies.Destination_C0312EA59B82EAC711175D8C037E196179
A49BDE8FF3F0D6830DDB66725C841B
- DestinationId: 1
- TravelWarnings: Take a hat!
Deleting an existing Model.Lodging
- LodgingId: 1
As expecteu, logging inloimation is piinteu out loi the thiee entities that aie allecteu
uuiing SaveChanges. You`ll notice that the Lodging we mouilieu has a stiange type
nameit`s not Model.Destination. This is Lecause we enaLleu Destination as a change
150 | Chapter 5:Change Tracker API
tiacking pioxy. The type name uisplayeu is the type that Entity Fiamewoik cieates at
iuntime that ueiives liom the Model.Destination type.
Using the Change Tracker API in Application Scenarios | 151
CHAPTER 6
Validating with the Validation API
Developeis olten spenu a lot ol time wiiting valiuation logic in theii applications. Many
ol the iules loi valiuation aie Luilt into theii classes, Lut .NET can`t magically veiily
those iules. Coue Fiist allows you to apply some iules uiiectly to piopeities using Data
Annotations oi the Fluent API. Foi example, you can specily the maximum length ol
a stiing oi the lact that a paiticulai piopeity is ieguiieu (i.e., can`t Le null).
Anothei type ol iule that youi mouel uesciiLes is ielationship constiaints. Foi example,
in oui mouel, a Lodging is ieguiieu to have a ielateu Destination. Entity Fiamewoik
has always checkeu that ielationship constiaint iules aie met Leloie it will push inseits,
upuates, oi ueletes to the uataLase.
The DbContext auus to this existing valiuation with the new Valiuation API that is as-
sociateu with the DbContext. Using the Valiuation API, the DbContext can automatically
(oi on uemanu) valiuate all ol the iules that you have uelineu using mechanisms that
the valiuation will iecognize. The API takes auvantage ol leatuies that alieauy exist
in .NET +ValidationAttributes anu the IValidatableObject. This integiation is a
gieat Lenelit to uevelopeis. Not only uoes it mean that you can leveiage existing ex-
peiience il you`ve woikeu with the leatuies alieauy, Lut it also means that Entity
Fiamewoik valiuation can llow into othei tools that use this class oi inteilace.
Valiuation in the uata layei is an impoitant element ol uata-locuseu applications. Vhile
you may have client-siue oi Lusiness layei valiuations, you may uesiie oi pielei to have
one last Lastion ol valiuation Leloie uata is pusheu into the uataLase. In scenaiios wheie
client-siue valiuation is peiloimeu in a weL application uepenuent on ]avaSciipt Leing
enaLleu in a Liowsei, uata layei valiuation plays an impoitant iole when the enu usei
has uisaLleu ]avaSciipt.
In this chaptei, you`ll leain how to take auvantage ol the Luilt-in valiuation pioviueu
Ly the DbContext anu Valiuation API using its uelault Lehaviois.
153
Defining and Triggering Validation: An Overview
The Valiuation API checks iules that you can apply in a numLei ol ways:
Piopeity attiiLute iules that you can specily using Data Annotations oi the lluent
API.
Custom valiuation attiiLutes that can Le uelineu loi piopeities oi types.
Rules uelineu in the Validate methou ol any mouel class that implements IValida
tableObject. This inteilace is pait ol .NET +, so it`s gieat to see that the DbCon
text was uesigneu to take auvantage ol this.
Relationship constiaints explicitly uelineu in the mouel.
Auuitionally, you can inject valiuations into the valiuation pipeline.
Theie aie a numLei ol ways to cause the DbContext to execute the valiuations:
By uelault, valiuation will Le peiloimeu on all tiackeu Added anu Modified oLjects
when you call SaveChanges.
The DbEntityEntry.GetValidationResult methou will peiloim valiuation on a sin-
gle oLject.
DbEntityEntry has a path loi valiuating an inuiviuual piopeity.
DbContext.GetValidationErrors will iteiate thiough each Added anu Modified oLject
Leing tiackeu Ly the DbContext anu valiuate each oLject.
In the next chaptei you`ll leain how to oveiiiue ValidateEntity as well
as change the uelault that valiuates only Added oi Modified oLjects.
At the ioot ol all ol these valiuation methous is the DbEntityEntry.GetValidationRe
sult methou, which valiuates the iules uelineu in piopeity attiiLutes anu IValidata
bleObjects. GetValidationErrors calls ValidateEntity on each Added oi Modified
tiackeu oLject, which in tuin calls GetValidationResult. SaveChanges calls GetValida
tionErrors, which means that the valiuation occuis automatically whenevei Save
Changes is calleu. Figuie 6-1 shows the wateilall path anu uilleient entiy points to
leveiage the Valiuation API.
The it just woiks appioach ol having valiuation implicitly calleu Ly SaveChanges may
Le all that some uevelopeis neeu oi aie inteiesteu in. But iathei than stait with the
appeaiance ol magic, we`ll use a Lottom-up appioach to show you the explicit valiua-
tion lunctionality so that you can use that to have moie contiol ovei how anu when
valiuation occuis.
154 | Chapter 6:Validating with the Validation API
Iigurc -1. Thrcc ways to cxccutc Gct\a|idationRcsu|t jron your codc
Validating a Single Object on Demand with
GetValidationResult
Oui sample classes alieauy have some attiiLutes that will Le checkeu Ly the Valiuation
API. Foi example, in the Destination class, you shoulu alieauy have a MaxLength an-
notation on the Description piopeity shown heie:
[MaxLength(500)]
public string Description { get; set; }
Relei to the listing loi Destination shown in Example 2-1.
Testing this iule won`t Le veiy easy since Lieaking it woulu mean auuing a stiing that
is gieatei than 500 chaiacteis. I uon`t leel like typing that much. Insteau, I`ll auu a new
MaxLength annotation to anothei piopeitythe LastName piopeity in the Person class:
[MaxLength(10)]
public string LastName { get; set; }
Now let`s see what happens when we set the length to a stiing with moie than ten
chaiacteis. GetValidationResult allows you to explicitly valiuate a single entity. It ie-
tuins a ValidationResult type that contains thiee impoitant memLeis. Ve`ll locus on
just one ol those loi now, the IsValid piopeity, which is a Boolean that inuicates il the
instance passeu its valiuation iules. Let`s use that to valiuate a Person instance. The
Validating a Single Object on Demand with GetValidationResult | 155
ValidateNewPerson methou in Example 6-1 shows calling the GetValidationRe
sult.IsValid methou.
Exanp|c -1. Mcthod to tcst va|idation oj LastNanc.
private static void ValidateNewPerson()
{
var person = new Person
{
FirstName = "Julie",
LastName = "Lerman",
Photo = new PersonPhoto { Photo = new Byte[] { 0 } }
};
using (var context = new BreakAwayContext())
{
if (context.Entry(person).GetValidationResult().IsValid)
{
Console.WriteLine("Person is Valid");
}
else
{
Console.WriteLine("Person is Invalid");
}
}
}
Il you iun this methou liom the Main methou, you will see the message Peison is Valiu
in the console winuows.
The GetValidationResult methou calls the necessaiy logic to valiuate any Validatio
nAttributes uelineu on the oLject`s piopeities. It then looks to see il the type has a
CustomValidationAttribute oi implements the IValidatableObject inteilace anu il it
uoes, calls its Validate methou. You`ll see this in action latei in this chaptei.
Vhile we stiongly iecommenu against calling uata access coue uiiectly
in the usei inteilace, these examples aie solely loi the puipose ol uem-
onstiating the Valiuation API leatuies. Ve aie not suggesting that you
use the DLContext loi peiloiming client-siue valiuation.
Now change the coue so that it sets LastName to Leiman-Flynn insteau ol Leiman.
Run the app again anu you will see Peison is Invaliu in the console. The Valiuation
API ol the DbContext was aLle to uetect that a iule was Lioken. This is just a high-level
look at the methou. Let`s exploie moie ways to ueline iules Leloie we uig luithei into
the iesult ol the valiuation.
156 | Chapter 6:Validating with the Validation API
What About Lazy Loading During Validation?
Il you have lazy loauing enaLleu on youi context, you uon`t neeu to woiiy aLout auveise
ellects ol lazy loauing uuiing valiuation; GetValidationResult will uisaLle lazy loauing
piioi to executing the valiuations. Then, when it has completeu its woik, it will iestoie
the DbContext.Configure.LazyLoadingEnabled Boolean piopeity to its oiiginal state.
Specifying Property Rules with ValidationAttribute Data
Annotations
The MaxLength Data Annotation is exposeu via the MaxLengthAttribute class. MaxLength
Attribute is one ol a gioup ol attiiLutes that inheiit liom a class calleu System.Data.Anno
tations.ValidationAttribute. GetValidationResult checkeu MaxLength Lecause it`s ue-
signeu to check any iule that is applieu using a ValidationAttribute.
The Valiuation API will check any iule that is applieu using a ValidationAttribute.
Following is a list ol the attiiLute classes that ueiive liom ValidationAttribute along
with the annotation useu to uecoiate a class piopeity:
DataTypeAttribute
[DataType(DataType enum)]
RangeAttribute
[Range (low value, high value, error message string)]
RegularExpressionAttribute
[RegularExpression(@expression)]
RequiredAttribute
[Required]
StringLengthAttribute
[StringLength(max length value,
MinimumLength=min length value)]
CustomValidationAttribute
This attiiLute can Le applieu to a type as well as to a piopeity.
Foi the sake ol uesciiLing uataLase schema mappings, the Entity Fiamewoik team
auueu MaxLengthAttribute to the namespace anu paiieu it with a new MinLengthAttri
bute. They Loth ueiive liom ValidationAttribute, so these too will Le checkeu Ly the
Valiuation API.
Both MaxLength anu Required aie not just ways to ueline a class piopeity Lut they aie
also useu to uesciiLe a uataLase column to which the piopeities map. Technically, these
aie ieleiieu to as lacets. Theieloie, in Entity Fiamewoik, these two lacets play uual
iulesthey help Coue Fiist unueistanu what the mappeu uataLase columns look like
Specifying Property Rules with ValidationAttribute Data Annotations | 157
anu they also paiticipate in the class-level valiuation. An auueu Lenelit is that since you
can also ueline these two lacetsMaxLength anu Requiredwith the Fluent API, Entity
Fiamewoik will take auvantage ol the ielevant ValidationAttribute types unuei the
coveis to make suie they get valiuateu as il they hau Leen conliguieu with the Data
Annotations.
Entity Fiamewoik has Leen taught to look loi the StringLength annotation anu use its
MaximumLength paiametei as a uataLase column lacet as well.
Validating Facets Configured with the Fluent API
Il you have useu the Fluent API to conliguie youi Coue Fiist mouel, you may Le lamiliai
with specilying attiiLutes lluently insteau ol using Data Annotations.
The Progranning Entity Irancwor|: Codc Iirst Look coveis lluent API
in uetail.
Two ol the Data Annotations that inheiit liom ValidationAttributeMaxLength anu
Requiredhave Fluent API counteipaits. This is uue to the lact that MaxLength anu
Required aie attiiLutes that impact the mouel`s compiehension ol the uataLase schema
anu theieloie impact how Entity Fiamewoik maps the classes to the uataLase.
The Valiuation API will check these two iules il you conliguie them with the Fluent
API. Foi example, you coulu ieplace the [MaxLength] annotation on Person.LastName
with this coue auueu into the BreakAwayContext.OnModelCreating methou:
modelBuilder.Entity<Person>()
.Property(p => p.LastName).HasMaxLength(10)
Il you ietuin to the ValidateNewPerson methou liom Example 6-1, ensuie the Last
Name piopeity is set to Leiman-Flynn, anu then ieiun the methou, it will iesult in a
message liom the console application: Peison is Invaliu.
Unueineath the coveis, Entity Fiamewoik is using the StringLengthAttribute (oi in
the case ol a Required scalai, the RequiredAttribute) to valiuate the HasMaxLength lacet
ol Person.FirstName. Although the example only checks the IsValid piopeity, the ue-
tails ol the eiioi aie ietuineu Ly GetValidationResult, anu you`ll see how to ieau these
shoitly.
Validating Unmapped or Transient Properties
It is possiLle to have piopeities in youi class that uo not map to the uataLase. By con-
vention, piopeities that uo not have Loth a settei anu a gettei will not Le pait ol the
mouel. These aie also known as transicnt propcrtics. You can also conliguie a piopeity
158 | Chapter 6:Validating with the Validation API
to Le unmappeu using the NotMapped uata annotation oi the Ignore lluent methou. By
uelault, unmappeu piopeities will not get valiuateu.
Howevei, il you have applieu a ValidationAttribute to a tiansient piopeity (as long as
that piopeity is in a class that is pait ol the mouel), Entity Fiamewoik will valiuate
those iules as well.
Validating Complex Types
Entity Fiamewoik`s conceptual mouel suppoits the use ol complex types, also known
as value oLjects. You can conliguie a complex type Loth in the Entity Data Mouel
uesignei as well as with Coue Fiist. It is also possiLle (anu leasiLle) to apply attiiLutes
to the piopeities ol complex types. Entity Fiamewoik`s GetValidationResult will val-
iuate attiiLutes placeu on complex type piopeities.
Using Data Annotations with an EDMX Model
It`s easy to apply uata annotations to youi class anu then use that class with Entity
Fiamewoik thanks to Coue Fiist, Lut what il you aie using the Entity Data Mouel
uesignei to cieate youi DataLase Fiist oi Coue Fiist mouel anu then ielying on coue
geneiation to cieate youi classes? Theie`s no oppoitunity to apply Data Annotations
to youi piopeities. You might mouily the T+ template to apply Data Annotations that
lollow veiy common patteins in youi classes, Lut typically this is not the appiopiiate
mechanism loi applying piopeity-Ly-piopeity attiiLutes.
The geneiateu classes aie paitial classes, which uoes give you the aLility to auu moie
logic to the classes with auuitional paitial classes. Howevei, you cannot auu attiiLutes
in one paitial class to piopeities that aie ueclaieu in anothei paitial class.
But all is not lost. Theie is a leatuie in .NET calleu an associatcd nctadata c|ass that
allows you to auu metauata to classes in an exteinal lile. These classes aie commonly
ieleiieu to as Luuuy classes, although we aie moie lonu ol the teim ugly Luuuy
classes Lecause they leel a little kluugy. Howevei ugly, they aie a gieat way to apply
uata annotations to geneiateu coue. So setting asiue illusions ol gianueui aLout oui
coue, let`s take a look at a simple example ol an associateu metauata class.
Daviu ELLo has an inteiesting Llog post on othei ways to use the
Metadata attiiLute: http://b|ogs.nsdn.con/b/davidcbb/archivc/2009/07/
21/using-an-associatcd-nctadata-c|ass-outsidc-dynanic-data.aspx
Il you aie using the DLContext Geneiatoi template to geneiate classes liom an EDMX,
the Person class will Le ueclaieu as a paitial class:
public partial class Person
Specifying Property Rules with ValidationAttribute Data Annotations | 159
anu the scalai piopeities will Le simple. Heie is what the FirstName piopeity will look
like:
public string FirstName { get; set; }
You can cieate a new class wheie you can mimic the piopeity ueclaiation anu apply
the attiiLute:
class Person_Metadata {
[MinLength(10)]
public string FirstName { get; set; }
}
Then you neeu to let the Person class know to use the Person_Metadata class loi the sole
puipose ol ieauing the attiiLutes.
You uo this Ly applying the Metadata attiiLute to the Person class:
[MetadataType(typeof(Person_Metadata))]
public partial class Person
Il you want to tiy this out in the Coue Fiist sample you`ve Leen woiking
with, Le suie to iemove oi comment out the MinLength annotation on
the FirstName piopeity in the Person class.
Inspecting Validation Result Details
Notice that GetValidationResult uoesn`t simply thiow an exception il the valiuation
lails. Insteau, it ietuins a System.Data.Entity.Validation.DbEntityValidationResult
whethei the iule is met oi Lioken, setting IsValid to the appiopiiate value anu pio-
viuing uetaileu inloimation on any Lioken iules.
DbEntityValidationResult also exposes a ValidationErrors piopeity, which contains
a collection ol moie uetaileu eiiois in the loim ol DbValidationError types. One linal
piopeity ol DbEntityValidationResult is a pointei. In this scenaiio, it seems ieuunuant
to have the Entry piopeity when we staiteu with the Entry to get the iesults. Howevei,
when one ol the highei-level methous calls GetValidationResult on youi Lehall, you
may not know which Entry is cuiiently Leing valiuateu; in that scenaiio, you`ll pioLaLly
Le giatelul loi the Entry piopeity.
Figuie 6-2 shows getting to the Entry, IsValid, anu ValidationErrors piopeities in the
ueLuggei.
160 | Chapter 6:Validating with the Validation API
Iigurc -2. Entry, |s\a|id and \a|idationErrors a va|idation rcsu|t
Inspecting Individual Validation Errors
Looking Lack at Figuie 6-2, you`ll see that when we set the LastName to Lerman-Flynn,
which exceeueu the MaxLength(10) specilication, the iesult`s ValidationErrors collec-
tion contains a single DbValidationError. DbValidationError exposes two piopeities,
the name ol the piopeity anu the actual eiioi message.
Vheie uiu the eiioi message come liom? The inteinal valiuation logic has a loimula
that composes a message using the piopeity name anu the annotation that laileu. This
is uelault Lehavioi.
You can specily youi own eiioi message in any ValidationAttribute. Foi example, il
you weie wiiting eiioi messages loi an application useu Ly suileis, you might want to
specily one like this:
[MaxLength(10,
ErrorMessage= "Dude! Last name is too long! 10 is max.")]
public string LastName { get; set; }
Figuie 6-3 shows the new ErrorMessage ietuineu in a DbEntityValidationError.
Iigurc -3. ErrorMcssagc and PropcrtyNanc oj a Db\a|idationError
Because the ValidationAttribute type is pait ol .NET + anu not specilic
to Entity Fiamewoik, we won`t spenu a lot ol time going into gieat uetail
aLout how to conliguie the ValidationAttribute types. Othei .NET
liamewoiks, such as Manageu ExtensiLility Fiamewoik (MEF),
ASP.NET MVC, anu ASP.NET Dynamic Data, use this lunctionality,
anu theie is a lot ol inloimation availaLle. To stait, heie is the MSDN
Topic on the ValidationAttribute class: http://nsdn.nicrosojt.con/cn
-us/|ibrary/systcn.conponcntnodc|.dataannotations.\a|idationAttri
butc.aspx.
Inspecting Validation Result Details | 161
As mentioneu in the eailiei note, MEF, MVC, anu Dynamic Data aie aLle to leveiage
the ValidationAttribute. Although the ValidateNewPerson methou uemonstiateu using
DbContext to peiloim the valiuation, it is also possiLle to valiuate ValidationAttri
butes uiiectly using its Valiuate methou. This is a gieat Lenelit loi client-siue uevelop-
ment. Howevei, since the locus ol this Look is DbContext, we`ll locus on how the
DbContext woiks with the ValidationAttributes at the uata layei. RememLei that
DbContext checks moie than just ValidationAttributes, so you can Lenelit liom these
as well as othei iules all at once on the seivei siue with DbContext.
Il you aie calling GetValidationResults uiiectly, you will have to wiite youi own logic
to inteiact with the DbEntityValidationResult, ieau the eiiois, anu hanule them,
whethei loi logging oi ietuining to the UI to piesent to the usei.
In the case ol the valiuation uetecteu in ValidateNewPerson, theie is a single eiioi insiue
the ValidationErrors piopeity. You coulu get to it Ly ieguesting that liist eiioi. Foi
example:
var result = context.Entry(person).GetValidationResult();
if (!result.IsValid)
{
Console.WriteLine(
result.ValidationErrors.First().ErrorMessage);
}
That will woik il you only expect oi only caie aLout the liist eiioi. Howevei, il you
have numeious ValidationAttributes uelineu on a type anu moie than one is Lioken,
theie will Le moie than one DbEntityValidationError in the ValidationErrors piopeity.
Vhat il the Person class also hau a iule that the FirstName piopeity must Le at least
thiee chaiacteis?
[MinLength(3)]
public string FirstName { get; set; }
Il you weie to upuate the ValidateNewPerson methou liom Example 6-1 to inseit just
the lettei ] as the FirstName, the valiuation will see two eiiois, as shown in Figuie 6-+.
Iigurc -1. Two crrors insidc oj thc rcsu|t`s \a|idationErrors propcrty
It might Le wisei, theieloie, to iteiate thiough the eiiois. You`ll neeu to auu a using
statement loi the System.Data.Entity.Validation namespace:
162 | Chapter 6:Validating with the Validation API
foreach (DbValidationError error in result.ValidationErrors)
{
Console.WriteLine(error.ErrorMessage);
}
Simplifying the Console Test Methods
Rathei than iewiite the valiuation iesult inspection in each methou, we`ve encapsulateu
that coue into a stanuaiu methou, ConsoleValidateResults, which you`ll see going loi-
waiu. Heie`s that coue il you want to use it, too:
private static void ConsoleValidationResults(object entity)
{
using (var context = new BreakAwayContext())
{
var result = context.Entry(entity).GetValidationResult();
foreach (DbValidationError error in result.ValidationErrors)
{
Console.WriteLine(error.ErrorMessage);
}
}
}
Exploring More ValidationAttributes
So lai we`ve lookeu at the MaxLength piopeity, which is not only a ValidationAttri
bute, Lut is an attiiLute that`s in the EntityIrancwor| assemLly. Let`s look at an at-
tiiLute that is not specilic to Entity Fiamewoik, the RegularExpressionAttribute, anu
veiily that the DbContext.GetValidationResult will see that as well.
Following is a RegularExpression applieu to the Destination.Country piopeity. This
expiession specilies that the stiing can Le up to +0 chaiacteis anu will accept uppeicase
anu loweicase letteis:
[RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
public string Country { get; set; }
The ValidateDestinationRegEx methou in Example 6-2 cieates a new Destination anu
asks the DbContext to valiuate the instance.
You`ll notice that we`ve also ielactoieu the methou to move the call to
GetValidationResult anu Console.WriteLine into a sepaiate methou
calleu ConsoleValidationResults. See the siueLai Simplilying the Con-
sole Test Methous on page 163 to see this new methou.
Exanp|c -2. Thc \a|idatcDcstination ncthod
public static void ValidateDestination()
{
ConsoleValidationResults(
Exploring More ValidationAttributes | 163
new Destination
{
Name = "New York City",
Country = "USA",
Description = "Big city"
});
}
Vith the Country set to USA, the piopeity is valiu anu no eiiois aie uisplayeu. How-
evei, il you change the Country value to U.S.A., GetValidationResult uetects an eiioi
Lecause the peiious in Letween the letteis uo not lollow the iule uelineu Ly the iegulai
expiession. Be awaie that the uelault eiioi message only iepoits that the value uoes not
match the expiession; it uoes not tell you which pait ol the expiession was Lioken:
The field Country must match the regular expression '^[a-zA-Z''-'\s]{1,40}$'.
This is not a pioLlem with how Entity Fiamewoik hanules the valiuation. It is simply
how the RegularExpressionAttribute Lehaves Ly uelault. You can leain moie aLout
contiolling this eiioi message in the MSDN uocumentation ieleienceu aLove.
Using CustomValidationAttributes
You can Luilu custom valiuation logic that can Le applieu to a piopeity using a Custom
ValidationAttribute. These too will get checkeu uuiing Entity Fiamewoik valiuation.
Example 6-3 shows an example ol a static class, BusinessValidations, which contains
a single valiuation, DescriptionRules, to Le useu on vaiious uesciiption piopeities in
the mouel. The iule checks loi exclamation points anu a lew emoticons to ensuie that
tiip uesciiptions oi othei uesciiptions uon`t ieau as though they weie text messages!
Exanp|c -3. Static custon va|idations to bc uscd by dijjcrcnt c|asscs
using System.ComponentModel.DataAnnotations;
namespace Model
{
public static class BusinessValidations
{
public static ValidationResult DescriptionRules(string value)
{
var errors = new System.Text.StringBuilder();
if (value != null)
{
var description = value as string;
if (description.Contains("!"))
{
errors.AppendLine("Description should not contain '!'.");
}
if (description.Contains(":)") ||
description.Contains(":("))
{
errors.AppendLine(
164 | Chapter 6:Validating with the Validation API
"Description should not contain emoticons.");
}
}
if (errors.Length > 0)
return new ValidationResult(errors.ToString());
else
return ValidationResult.Success;
}
}
}
The ValidationResult useu heie is a System.ComponentModel.DataAnno
tations.ValidationResult, not to Le conluseu with the System.Win
dows.Controls.ValidationResult.
You can apply the valiuation to piopeities using the CustomValidationAttribute, as
shown in Example 6-+, wheie we`ve auueu the annotation to the Destination.Descrip
tion piopeity (which alieauy has a MaxLength annotation). The attiiLute ieguiies that
you specily the class wheie the valiuation methou exists anu then the name ol the
methou as a stiing.
Exanp|c -1. App|ying thc ncw DcscriptionRu|cs va|idation to a propcrty
[MaxLength(500)]
[CustomValidation(typeof(BusinessValidations), "DescriptionRules")]
public string Description { get; set; }
Il you`u like to test out the valiuation, you can mouily the ValidateDestination methou
to inseit some ol the unuesiiaLle chaiacteis into the Description stiing, as we`ve uone
in Example 6-5.
Exanp|c -5. Crcating a Dcstination that brca|s nu|tip|c va|idation ru|cs
public static void ValidateDestination()
{
ConsoleValidationResults(
new Destination {
Name = "New York City",
Country = "U.S.A",
Description = "Big city! :) "
});
}
Executing the methou will cause the valiuation to ietuin the lollowing list ol eiiois:
The field Country must match the regular expression '^[a-zA-Z''-'\s]{1,40}$'.
Description should not contain '!'.
Description should not contain emoticons.
Both the RegularExpression valiuation on the Country piopeity anu the Description
Rules custom valiuation on Description aie iepoiteu.
Exploring More ValidationAttributes | 165
Validating Individual Properties on Demand
In auuition to pioviuing the GetValidationResults methou, DbEntityEntry lets you uiill
into inuiviuual piopeities, as you`ve alieauy seen in Chaptei 5:
context.Entry(trip).Property(t => t.Description);
This ietuins a DbPropertyEntry iepiesenting the Description piopeity.
The DbPropertyEntry class has a methou loi explicitly valiuating that paiticulai entiy
GetValidationErrorswhich will ietuin an ICollection<DbValidationError>. This is
the same DbValidationError class we`ve Leen exploiing alieauy in this chaptei.
Example 6-6 uisplays a new methou, ValidatePropertyOnDemand, which shows how to
valiuate a piopeity using DbPropertyEntry.GetValidationErrors. You`ll liist neeu to
apply the DescriptionRules custom attiiLute to the Trip.Description piopeity, just as
you uiu loi Destination.Description in Example 6-+.
Exanp|c -. \a|idating a propcrty
private static void ValidatePropertyOnDemand()
{
var trip=new Trip
{
EndDate = DateTime.Now,
StartDate = DateTime.Now,
CostUSD = 500.00M,
Description = "Hope you won't be freezing :)"
};
using (var context = new BreakAwayContext())
{
var errors = context.Entry(trip)
.Property(t => t.Description)
.GetValidationErrors();
Console.WriteLine("# Errors from Description validation: {0}",
errors.Count());
}
}
The methou cieates a new Trip that has an emoticon in the Description. Baseu on the
custom valiuation iule you cieateu eailiei in this chaptei, the emoticon is invaliu.
Il you weie to call this methou liom the Main methou in the console application, the
console winuow woulu iepoit that theie is one eiioi in the Description. Keep this
methou aiounu, Lecause we`ll look at it again in the next section.
Specifying Type-Level Validation Rules
Vhile theie aie moie ways to tiiggei valiuations, let`s stick with the GetValidationRe
sult methou while we look at othei ways to pioviue iules that the Valiuation API will
valiuate. So lai you`ve seen how to apply valiuation iules on inuiviuual piopeities. You
166 | Chapter 6:Validating with the Validation API
can also ueline iules loi a type that can take multiple piopeities into account. Two
ways to cieate type-level valiuation that will Le checkeu Ly the Entity Fiamewoik Val-
iuation API aie Ly having youi type implement the IValidatableObject inteilace oi
uelining CustomValidationAttributes loi type. This section will exploie Loth ol these
options.
Using IValidatableObject for Type Validation
In auuition to the ValidationAttribute, .NET + intiouuceu anothei leatuie to help
uevelopeis with valiuation logicthe IValidatableObject inteilace. IValidatableOb
ject pioviues a Validate methou to let uevelopeis (oi liamewoiks) pioviue theii own
context liom which to peiloim the valiuation.
Il an entity that is Leing valiuateu implements the IValidatableObject inteilace, the
Valiuation API logic will iecognize this, call the Valiuate methou, anu suilace the iesults
ol the valiuation in a DbEntityValidationError.
Vhat uoes IValidatableObject pioviue that is not satislieu with Data Annotations?
The Data Annotations let you specily a limiteu numLei ol iules loi inuiviuual piopei-
ties. Vith the auuitional Validate methou, you can pioviue any type ol logic that can
Le constiaineu to the class. Vhat we mean Ly constiaineu is that the valiuation logic
won`t iely on exteinal oLjects since you can`t guaiantee that they`ll Le availaLle when
the valiuation is Leing peiloimeu. A typical example is compaiing uate piopeities in a
class.
Validations and Your Application Architecture
The IValidatableObject.Validate methou can Le calleu liom any pait ol youi appli-
cation that has access to the .NET liamewoik. You can even leveiage it loi client-siue
valiuation. In youi application aichitectuie, you shoulu Le consiueiate ol wheie you
aie uepenuing on the DbContext to peiloim valiuation logic. Most likely, the context
will Le on the seivei siue ol youi application anu you woulu not want to iely on youi
uata access layei loi client-siue valiuation in youi usei inteilace. Like the ValidationAt
tributes, IValidatableObject gives you the option to peiloim youi valiuation in what-
evei layei ol youi application makes the most sense loi youi aichitectuie anu scenaiio.
The Trip type has StartDate anu EndDate lielus. Let`s use IValidatableObject to ueline
a iule that EndDate must Le gieatei than StartDate.
Example 6-7 shows the Trip class altei we`ve auueu the IValidatableObject imple-
mentation that incluues the Validate methou. Validate compaies the uates anu ietuins
a ValidationResult il the iule is Lioken. Notice that we`ve also auueu the Description
Rules attiiLute we cieateu in Example 6-3 to the Description lielu.
Specifying Type-Level Validation Rules | 167
Exanp|c -7. \a|idating datcs in an |\a|idatab|cObjcct.\a|idatc ncthod
public class Trip : IValidatableObject
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Identifier { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
[CustomValidation(typeof(BusinessValidations), "DescriptionRules")]
public string Description { get; set; }
public decimal CostUSD { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
public int DestinationId { get; set; }
[Required]
public Destination Destination { get; set; }
public List<Activity> Activities { get; set; }
public IEnumerable<ValidationResult> Validate(
ValidationContext validationContext)
{
if (StartDate.Date >= EndDate.Date)
{
yield return new ValidationResult(
"Start Date must be earlier than End Date",
new[] { "StartDate", "EndDate" });
}
}
}
Visual Basic (VB) uoes not have a yielu keywoiu. Insteau, you can cieate a List<Vali
dationResult>, auu each ValidationResult into that list, anu then ietuin it. Exam-
ple 6-S shows the Validate methou as you woulu wiite it in VB.
Exanp|c -8. Thc \a|idatc ncthod cxprcsscd in \isua| Basic
Public Function Validate(
ByVal validationContext As ValidationContext)
As IEnumerable(Of ValidationResult)
Implements IValidatableObject.Validate
Dim results = New List(Of ValidationResult)
If StartDate.Date >= EndDate.Date Then
results.Add(New ValidationResult
("Start Date must be earlier than End Date",
{"StartDate", "EndDate"}))
End If
Return result
End Function
Ve`ve auueu a new methou to the console application calleu ValidateTrip, shown in
Example 6-9.
168 | Chapter 6:Validating with the Validation API
Exanp|c -9. Thc \a|idatcTrip ncthod to chcc| thc ncw ru|c
private static void ValidateTrip()
{
ConsoleValidationResults(new Trip
{
EndDate = DateTime.Now,
StartDate = DateTime.Now.AddDays(2),
CostUSD = 500.00M,
Destination = new Destination { Name = "Somewhere Fun" }
});
}
Vhen calling ValidateTrip, the application uisplays the eiioi message, Stait Date
must Le eailiei than Enu Date. But it`s listeu twice. That`s Lecause the Valiuate methou
listeu this as a pioLlem loi Loth StartDate anu EndDate, so it cieateu two sepaiate eiiois.
The DbValidationError.ErrorMessage is the same in Loth, Lut one has EnuDate in its
DbValidationError.PropertyName while the othei has StaitDate.
This is impoitant loi uata Linuing with liamewoiks such as MVC oi VPF wheie you
can Linu the eiiois to the uisplayeu piopeities. Il we mouily the ValidateTrip methou
to ensuie that EndDate is a latei uate than StartDate, the ValidateTrip methou ietuins
no eiioi messages.
Mappeu complex types that implement IValidatableObject will Le
checkeu in the valiuation pipeline as well.
Validating Multiple Rules in IValidatableObject
You can auu as many class valiuations as you like in youi Validate methou. Vith the
C= yielu keywoiu, all ol the ValidationResult types cieateu will Le containeu within
the IEnumerable that`s ietuineu Ly the methou.
Example 6-10 shows the Trip.Validate methou with a seconu valiuation auueu that
checks against a list ol woius that aie unuesiiaLle loi uesciiLing tiips. You coulu use
a RegularExpression annotation with the woiu list, Lut this methou gives you the op-
poitunity to stoie the list ol woius in a iesouice lile so that it`s not haiu-coueu into the
application. The list is haiu-coueu into this example only loi the simplicity ol uemon-
stiating the valiuation. You`ll neeu to auu a using statement loi the System.Linq name-
space.
Exanp|c -10. \a|idatc ncthod jor thc Trip typc
public IEnumerable<ValidationResult> Validate(
ValidationContext validationContext)
{
if (StartDate.Date >= EndDate.Date)
{
Specifying Type-Level Validation Rules | 169
yield return new ValidationResult(
"Start Date must be earlier than End Date",
new[] { "StartDate", "EndDate" });
}
var unwantedWords = new List<string>
{
"sad",
"worry",
"freezing",
"cold"
};
var badwords = unwantedWords
.Where(word => Description.Contains(word));
if (badwords.Any())
{
yield return new ValidationResult(
"Description has bad words: " + string.Join(";", badwords),
new[] { "Description" });
}
}
Now we`ll mouily the ValidateTrip methou to auu a Description (which incluues the
unuesiiaLle woius lieezing anu woiiy) to the new tiip Leloie the valiuation is
peiloimeu (Example 6-11).
Exanp|c -11. \a|idatcTrip ncthod nodijicd to inc|udc Dcscription
private static void ValidateTrip()
{
ConsoleValidationResults(new Trip
{
EndDate = DateTime.Now,
StartDate = DateTime.Now.AddDays(2),
CostUSD = 500.00M,
Description="Don't worry about freezing on this trip",
Destination = new Destination { Name = "Somewhere Fun" }
});
}
Vhen iunning ValidateTrip with this tiip that now Lieaks two iules, Loth eiioi mes-
sages aie uisplayeu in the console winuow:
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Description has bad words: worry;freezing
In the pievious section, Valiuating Inuiviuual Piopeities on Demanu on page 166,
you cieateu a methou to exploie the DbPropertyEntry.GetValidationErrors. Looking
Lack at Example 6-6, notice that in auuition to the emoticon in the uesciiption, theie
is what you now know to Le an unuesiiaLle woiufreezing. Il you weie to iun the
methou again, the console winuow woulu still only iepoit a single eiioi, which is a
170 | Chapter 6:Validating with the Validation API
iesult ol the emoticon. It seems to ignoie the pioLlem with the woiu freezing. That`s
Lecause the valiuation that checks loi the woiu freezing is uelineu loi the class. DbPro
pertyEntry.GetValidationErrors can only check ValidationAttributes placeu on
piopeities.
Should You Use Validate for Other Purposes?
Applications olten tiack when uata was liist cieateu oi last upuateu. Upuating lielus
like DateAdded anu DateLastModified is something that coulu uemanu ieuunuant logic.
It may Le tempting to place that logic into Validate, Lut iememLei that Validate must
ietuin an IEnumerable<ValidationResult>. Youi coue woulu stait to get pietty messy il
you hau to ietuin lake iesults. You`u Le Lettei oll aLstiacting logic like this elsewheie.
In the scope ol Entity Fiamewoik`s Valiuation API, theie`s a moie impoitant ieason to
avoiu it, which you`ll leain aLout in uetail in Chaptei 7. Because ol the way Save
Changes, DetectChanges, anu valiuation inteiact with one anothei, you coulu get unex-
pecteu Lehavioi anu incoiiectly peisisteu uata il you mouily values in youi valiuation
logic.
Using CustomValidationAttributes for Type Validation
You can also use CustomValidationAttribute on a type iathei than an inuiviuual piop-
eity, allowing you to ueline a valiuation that takes into account moie than a single
piopeity. Ve`ll show you how you can ueline the same valiuation in the IValidata
bleObject example aLove Ly using CustomValidationAttribute.
The signatuie ol a CustomValidationAttribute has the taiget type specilieu in the pa-
iametei along with a ValidationContext, which is useu in the same way as the Valida
tionContext paiametei ol the Validate methou. Example 6-12 shows two valiuation
methous that aie auueu into the Trip class. Notice that these methous aie Loth pub
lic anu static. Also notice that we`ie using sepaiate methous loi each valiuation.
That`s Lecause a ValidationAttribute can only ietuin a single ValidationResult.
Exanp|c -12. Two va|idation ncthods to bc uscd as Trip typc attributcs
public static ValidationResult TripDateValidator(
Trip trip,
ValidationContext validationContext)
{
if (trip.StartDate.Date >= trip.EndDate.Date)
{
return new ValidationResult(
"Start Date must be earlier than End Date",
new[] { "StartDate", "EndDate" });
}
return ValidationResult.Success;
}
Specifying Type-Level Validation Rules | 171
public static ValidationResult TripCostInDescriptionValidator(
Trip trip,
ValidationContext validationContext)
{
if (trip.CostUSD > 0)
{
if (trip.Description
.Contains(Convert.ToInt32(trip.CostUSD).ToString()))
{
return new ValidationResult(
"Description cannot contain trip cost",
new[] { "Description" });
}
}
return ValidationResult.Success;
}
The liist methou, TripDateValidator, mimics a valiuation you useu eailieichecking
that the StartDate is eailiei than the EndDate. The seconu methou, TripCostInDescrip
tionValidator, checks to make suie that a usei hasn`t wiitten the tiip cost into the
uesciiption. The logic in that methou coulu Le line-tuneu loi a piouuction application,
Lut it shoulu sullice loi this uemonstiation.
Theie`s a notaLle uilleience with these methous when compaiing them to the Vali
date methou you saw eailiei. The Valiuate methou has access to piivate methous,
piopeities, anu lielus. But Lecause ol the way the ValidationAttribute is hanuleu unuei
the coveis tiiggeiing those methous (which is also why they must Le public anu
static), it will not have this access.
To have Loth valiuations executeu, you neeu to auu them as sepaiate attiiLutes on the
Tiip class, as shown in Example 6-13.
Exanp|c -13. App|ying a typc-|cvc| Custon\a|idationAttributc
[CustomValidation(typeof(Trip), "TripDateValidator")]
[CustomValidation(typeof(Trip), "TripCostInDescriptionValidator")]
public class Trip: IValidatableObject
Il you weie to iun the ValidateTrip methou, the console winuow woulu uisplay this:
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Description has bad words: worry;freezing
As a ieminuei, Lecause the valiuatoi cieates the ValidationResult specilying that it`s
loi Loth the StartDate lielu anu loi the EndDate piopeity, the eiioi is listeu once loi
each piopeity. Il you weie to inspect the ValidationResult moie closely, you woulu see
that the eiiois aie uilleientiateu Ly theii PropertyName. You`ie also seeing the eiiois
172 | Chapter 6:Validating with the Validation API
geneiateu Ly the Validate methou. The Validate methou is also checking the uate iange
as well as checking the Description loi unwanteu woius.
Mouily the ValidateTrip methou to Lieak the TripCostInDescriptionValidator iule as
lollows, changing the value ol the Description:
private static void ValidateTrip()
{
ConsoleValidationResults(new Trip
{
EndDate = DateTime.Now,
StartDate = DateTime.Now.AddDays(2),
CostUSD = 500.00M,
Description = "You should enjoy this 500 dollar trip",
Destination = new Destination { Name = "Somewhere Fun" }
});
}
Running the application again woulu iesult in the two eiiois liom the pioLlem with
the uate piopeities as well as the eiioi message liom the laileu TripCostInDescription
Validator valiuation:
Description cannot contain trip cost
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date
IValidatableObject or CustomValidationAttribute?
Goou guestion, anu it`s one that we`ve Leen asking the pios so we can ielay theii auvice
to you. You have two ways to ueline type valiuation iules: which one is Lest to use?
Natuially the guestion geneiateu some ueLate. So oui Lest guiuance within the context
ol this Look aLout Entity Fiamewoik is that it may simply Le a mattei ol peisonal
pieleience anu couing style. Il you aie alieauy using Data Annotations in youi class,
you might want to continue the pattein Ly using the attiiLute to pioviue type valiuation
iules. You may not Le a lan ol uecoiating youi classes with attiiLutes anu linu that
implementing IValidatableObject is moie appiopiiate loi youi coue.
Understanding How EF Combines Validations
RememLei the DescriptionRules methou that we auueu to the BusinessValidations
class to valiuate the Description piopeity ol Destination anu Trip? Those contain
oveiall iules loi wiiting any uesciiption loi the company, not just those loi uestinations.
Now we`ll mouily the Description in the ValidateTrip methou to incluue the uieaueu
smiley lace emoticon anu exclamation point:
Description="Hope you won't be freezing on this trip! :)"
Understanding How EF Combines Validations | 173
Beloie iunning the ValidateTrip methou again, keep in minu that the values ol this
Trip instance Lieak loui iules:
1. The StartDate is not at least a lull calenuai uay Leloie the EndDate.
2. The woiu freezing is in the uesciiption.
3. Theie is an emoticon in the Description.
+. Theie is an exclamation point in the Description.
Heie is the list ol messages ietuineu Ly the methou:
Description should not contain '!'.
Description should not contain emoticons.
The pioLlems aLout the uates anu the woiu freezing aie missing liom the messages.
To Le suie, let`s ieveit the Description, iemoving the exclamation anu emoticon so
that it passes the DescriptionRules Lut lails the otheis. This Liings us Lack to the uate
pioLlem listeu loi Loth the uate lielus anu the message aLout the woiu freezing:
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Start Date must be earlier than End Date
Description has bad words: freezing
Vhile this looks like theie`s a pioLlem with the valiuation, the valiuation is inueeu
woiking as uesigneu. Ve`ve uelineu Loth piopeity valiuation anu type valiuation. The
piopeity valiuations check loi the emoticons anu exclamation point, while the type
valiuations check the uates anu look loi Lau woius. The lailuie ol a piopeity valiuation
is shoit-ciicuiting the type valiuations. In othei woius, the type valiuation is nevei
peiloimeu Lecause pioLlems weie lounu when valiuating the piopeities.
Let`s upuate the ValidateTrip methou so that it no longei supplieu a value loi the
Destination piopeitywhich is maikeu with a Required attiiLute:
// Destination = new Destination { Name = "Somewhere Fun" }
Il you ieiun the ValidateTrip methou, which no longei pioviues a value loi the ieguiieu
Destination piopeity, the only eiioi message is this:
The Destination field is required.
The Required valiuation lailuie is iepoiteu, Lut the type valiuations aie still missing, so
the lailuie ol this valiuation also pieventeu the type valiuation. Il we auueu the ! :)
Lack into the Description, you`u see all ol the piopeity valiuation pioLlems listeu (Re-
guiieu, !, anu the emoticon) Lut still no iepoit ol the type valiuation pioLlems.
Vhat`s happening is that theie aie iules that the valiuation engine lollows that pievent
it liom eiioneously iepoiting eiiois that might Le causeu Ly othei valiuation eiiois. Il
the piopeity valiuation lails, it`s possiLle that the Lau attiiLutes might cause the type
valiuation to lail as well.
174 | Chapter 6:Validating with the Validation API
Boiiowing liom the Entity Fiamewoik team Llog post at http://b|ogs.nsdn.con/b/ado
nct/archivc/2011/05/27/cj-1-1-va|idation.aspx, heie is a uesciiption ol the oiuei in
which valiuations aie peiloimeu:
1. Piopeity-level valiuation on the entity anu the Lase classes. Il a piopeity is complex
its valiuation woulu also incluue the lollowing:
2. Piopeity-level valiuation on the complex type anu its Lase types
3. Type-level valiuation on the complex type anu its Lase types, incluuing IValidata
bleObject valiuation on the complex type
+. Type-level valiuation on the entity anu the Lase entity types, incluuing IValidata
bleObject valiuation
The key point is that type-level valiuation will not Le iun il piopeity valiuation ietuins
an eiioi. In auuition to the IValidatableObject valiuations, ielationship constiaints aie
valiuateu. Since it`s possiLle that one ol the piopeity lailuies was uue to a missing
ieguiieu piopeity, that null value coulu veiy easily cause a ielationship to Le invaliu.
The Valiuation API uoes not allow constiaint checking to occui il the piopeities cannot
Le valiuateu.
Aligning Validation with the Rest of .NET
ASP.NET MVC, ASP.NET Dynamic Data, VCF RIA Seivices, anu Manageu Extensi-
Lility Fiamewoik have theii own mechanisms loi leveiaging ValidationAttributes anu
IValidatableObject. Each ol these technologies has similai patteins loi ueteimining
the oiuei in which to peiloim valiuations anu how a lailuie ol one valiuation will impact
whethei oi not othei valiuations aie peiloimeu.
One ol the impoitant patteins is that type valiuation is not peiloimeu il piopeity val-
iuations lail. This is mostly uue to the possiLility ol theie Leing a Required piopeity.
Accoiuing to Pawel Kauluczka liom the Entity Fiamewoik team:
Ve wanteu to Le as close as possiLle to what they (e.g., MVC) uo loi consistency.
The ieason why they uo it is that none ol the Luilt-in valiuation attiiLutes tieat
null as an incoiiect valuee.g., loi StringLength, null will not Lieak. This makes
sense since null can Le tieateu eithei as incoiiect value oi as something that has
0 length. Choosing one ol these options aiLitiaiily woulu pioLaLly make hall ol
the uevelopeis using valiuation unhappy. Now they can choose on theii own the
Lehavioi they neeu.
Validating Multiple Objects
In auuition to explicitly valiuating a single oLject with GetValidationResult, you can
loice the context to valiuate all ol the ncccssary oLjects it is tiacking with a single
commanu: DbContext.GetValidationErrors. I`ve emphasizeu the woiu necessaiy Le-
cause, Ly uelault, GetValidationErrors only valiuates Added anu Modified oLjects since
Validating Multiple Objects | 175
it typically woulun`t Le necessaiy to valiuate oLjects that aie Unchanged oi aie maikeu
to Le ueleteu liom the uataLase.
Vhen you call this methou, the context will inteinally call DetectChanges to ensuie that
all ol the change tiacking inloimation is up-to-uate. Then it will iteiate thiough all ol
the Added anu Modified oLjects that it`s tiacking anu call DbContext.ValidateEntity on
each oLject. ValidateEntity, in tuin, will call GetValidationResult on the taiget oLject.
Vhen all ol the oLjects have Leen valiuateu, GetValidationErrors ietuins a collection
ol DbEntityValidationResult types loi eveiy laileu oLject. The collection is ietuineu as
an IEnumerable< DbEntityValidationResult > anu it only contains DbEntityValidation
Result instances loi the laileu oLjects.
In auuition to calling GetValidationResult, ValidateEntity can also call
custom logic that you specily. The next chaptei will locus on custom-
izing ValidateEntity. You`ll also leain to mouily how Entity Fiamewoik
ueciues which entities to valiuate Ly oveiiiuing the uelault, which only
valiuates Added anu Modified entities.
Example 6-1+ uisplays a methou that iesults in a context tiacking
two new Trips,
one new Destination,
anu one mouilieu Trip.
Il you look closely at the coue, you`ll see that one ol the new tiips will Le valiu while
the othei thiee oLjects aie not valiu.
Exanp|c -11. \a|idating nu|tip|c trac|cd objccts
private static void ValidateEverything()
{
using (var context = new BreakAwayContext())
{
var station = new Destination
{
Name = "Antartica Research Station",
Country = "Antartica",
Description = "You will be freezing!"
};
context.Destinations.Add(station);
context.Trips.Add(new Trip
{
EndDate = new DateTime(2012, 4, 7),
StartDate = new DateTime(2012, 4, 1),
CostUSD = 500.00M,
Description = "A valid trip.",
Destination = station
});
176 | Chapter 6:Validating with the Validation API
context.Trips.Add(new Trip
{
EndDate = new DateTime(2012, 4, 7),
StartDate = new DateTime(2012, 4, 15),
CostUSD = 500.00M,
Description = "There were sad deaths last time.",
Destination = station
});
var dbTrip = context.Trips.First();
dbTrip.Destination = station;
dbTrip.Description = "don't worry, this one's from the database";
DisplayErrors(context.GetValidationErrors());
}
}
Along with the ValidateEverything methou in Example 6-1+, auu the DisplayErrors
custom methou (Example 6-15) to the Program class. This will iteiate thiough the
DbEntityValidationResult oLjects ietuineu Ly the GetValidationErrors methou anu
uisplay them in a console winuow.
Exanp|c -15. Disp|ayErrors ncthod ca||cd jron Exanp|c 5-12
private static void DisplayErrors(
IEnumerable<DbEntityValidationResult> results)
{
int counter = 0;
foreach (DbEntityValidationResult result in results)
{
counter++;
Console.WriteLine(
"Failed Object #{0}: Type is {1}",
counter,
result.Entry.Entity.GetType().Name);
Console.WriteLine(
" Number of Problems: {0}",
result.ValidationErrors.Count);
foreach (DbValidationError error in result.ValidationErrors)
{
Console.WriteLine(" - {0}", error.ErrorMessage);
}
}
}
Mouily the Main methou to call ValidateEverything, which will execute anu uisplay the
valiuation iesults, as shown in Example 6-16.
Validating Multiple Objects | 177
Exanp|c -1. Output jron \a|idatcEvcrything ncthod
Failed Object #1: Type is Destination
Number of Problems: 1
- Description should not contain '!'.
Failed Object #2: Type is Trip
Number of Problems: 5
- Start Date must be earlier than End Date
- Start Date must be earlier than End Date
- Start Date must be earlier than End Date
- Start Date must be earlier than End Date
- Description has bad words: sad
Failed Object #3: Type is Trip
Number of Problems: 1
- Description has bad words: worry
GetValidationErrors uoes not check ielationship constiaints unless they aie explicitly
conliguieu. Foi example, Ly uelault, the Reservation.Traveler piopeity is nullaLle.
Theie aie two ways to loice the Reservation to ieguiie that a Person type Le attacheu.
One is to auu an int TravelerId piopeity anu conliguie that to Le the loieign key loi
Traveler. Int is non-nullaLle Ly uelault. ValidateEntity will not check that constiaint
anu theieloie GetValidationErrors won`t eithei.
SaveChanges will uetect ielationship constiaint pioLlems even il they aie
not uelineu in a way that ValidateEntity will tiap them.
Anothei way to ieguiie that a Person Le attacheu is to conliguie the Traveler piopeity
as ieguiieu. Vith a ValidationAttribute (even il you`ve conliguieu with the Fluent
API), ValidateEntity will check this iule anu GetValidationErrors will uetect the pioL-
lem.
In Chaptei 3, you leaineu aLout DetectChanges, the events that call it Ly
uelault, anu how to uisaLle automatic change uetection. Il you have
uisaLleu change uetection, that means GetValidationErrors won`t call
it eithei anu you shoulu make an explicit call to DetectChanges Leloie
calling GetValidationErrors.
Validating When Saving Changes
Vhile you may pielei to have explicit contiol ovei when GetValidationResults is calleu,
Entity Fiamewoik can automatically peiloim the valiuations when you call Save
Changes. By uelault, when you call SaveChanges, each Added anu Modified entity that is
Leing tiackeu Ly the context will Le valiuateu Lecause SaveChanges calls
GetValidationErrors.
178 | Chapter 6:Validating with the Validation API
Reviewing ObjectContext. SaveChanges Workflow
Latei in this section, you`ll leain how to uisaLle the automatic valiuation that occuis
uuiing SaveChanges. You may alieauy Le lamiliai with how ObjectContext.Save
Changes woiks in the Entity Fiamewoik. Foi a Liiel oveiview, it lollows this woikllow
(note that this is not taking DbContext into account yet only the inteinal woikllow):
1. SaveChanges, Ly uelault, calls DetectChanges to upuate its tiacking inloimation on
POCO oLjects.
2. SaveChanges iteiates thiough each tiackeu entity that ieguiies some mouilication
(those with states Added, Modified, oi Deleted).
3. Foi each ol these entities, it checks that theii ielationship constiaints aie in a piopei
state. Il not, it will thiow an EntityUpdateException loi that entity anu stop luithei
piocessing.
+. Il all ol the entities pass the ielationship valiuation, EF constiucts anu executes the
necessaiy SQL commanu(s) to peiloim the coiiect action in the uataLase.
5. Il the uataLase commanu lails, the context iesponus Ly thiowing an EntityUpda
teException anu stops luithei piocessing.
Because SaveChanges uses a DbTransaction Ly uelault, in eithei ol the ciicumstances
that causes the ioutine to thiow an exception, any ol the commanus that succeeueu up
until that point aie iolleu Lack.
Understanding DbContext.SaveChanges Workflow
Vhen you use DbContext to call SaveChanges, one auuitional step is peiloimeu piioi to
the liist step in the ObjectContext.Savechanges woikllow. DbContext.SaveChanges calls
GetValidationErrors, which iuns thiough the ValidateEntity piocess. Il no eiiois aie
lounu, it then calls ObjectContext.SaveChanges. Because GetValidationErrors has al-
ieauy calleu DetectChanges, ObjectContext.SaveChanges skips its own call to DetectCh
anges.
Figuie 6-5 shows the execution path when youi coue calls DbContext.SaveChanges.
Validating When Saving Changes | 179
Iigurc -5. Databasc pcrsistcncc wor|j|ow bcginning with DbContcxt.SavcChangcs
Vhat this means to you is that, Ly uelault, Entity Fiamewoik will valiuate all ol the
iules specilieu with ValidationAttributes anu IValidatableObject automatically when
you call SaveChanges liom a DbContext.
Il eiiois aie uetecteu uuiing GetValidationErrors, SaveChanges will thiow a DbEntity
ValidationException with the iesults ol GetValidationErrors in its EntityValidationEr
rors piopeity. In this case, the context will nevei make the call to ObjectContext.Save
Changes.
In the pievious section, you leaineu that ValidationEntity, calleu Ly GetValidationEr
rors, will not check ielationship constiaints unless they aie specilieu in conliguiation.
Howevei, ObjectContext.SaveChanges has always checkeu ielationship constiaints anu
continues to uo so. Theieloie, any ielationship constiaints that weie not valiuateu Ly
GetValidationErrors will Le checkeu in the next stage ol the save. The same applies to
null complex piopeities. Since null complex piopeities aie not suppoiteu, ObjectCon
text always checks il a complex piopeity is not null. Having the Required attiiLute on
a complex piopeity makes sense only loi consistency ieasons (that is, a null complex
piopeity violation will Le iepoiteu the same way as othei valiuation violations).
180 | Chapter 6:Validating with the Validation API
Il you`u like to see this in action, you can mouily the ValidateEverything methou so
that iathei than explicitly calling GetValidationErrors, it will call SaveChanges. Replace
the linal line ol the ValidateEverything methou liom Example 6-1+ (i.e., the coue line
that calls into DisplayErrors) with the coue in Example 6-17. You`ll call SaveChanges
insteau anu then uisplay inloimation aLout any valiuation exceptions.
Exanp|c -17. \a|idatcEvcrything nodijicd to ca|| SavcChangcs instcad oj Gct\a|idationErrors
try
{
context.SaveChanges();
Console.WriteLine("Save Succeeded.");
}
catch (DbEntityValidationException ex)
{
Console.WriteLine(
"Validation failed for {0} objects",
ex.EntityValidationErrors.Count());
}
Because this example contains intentional pioLlems that will Le uetecteu uuiing the
inteinal call to GetValidationErrors, ObjectContext.SaveChanges will nevei Le executeu
anu youi uata will not Le peisisteu to the uataLase. Il the valiuations weie to pass,
theie`s still a chance ol an UpdateException when the lowei-level ObjectContext.Save
Changes is calleu inteinally, Lut we`ie ignoiing that possiLility in this example.
Il you weie to iun this methou, you woulu linu is that a DbEntityValidationExcep
tion is thiown. DbEntityValidationException has a piopeity calleu EntityValidatio
nErrors which ietuins an IEnumerable ol something you aie alieauy lamiliai with
EntityValidationResults that weie cieateu loi each laileu entity.
Figuie 6-6 shows the DbValidationException in the ueLug winuow (with piivate lielus
iemoveu loi claiity).
Iigurc -. |nspccting a Db\a|idationExccption
Validating When Saving Changes | 181
The exception hanulei in Example 6-17 uisplays how many EntityValidationResult
instances aie containeu in the exception, in othei woius, how many entities laileu val-
iuation when SaveChanges was calleu.
Because you alieauy know how to iteiate thiough EntityValidationResult oLjects, you
can uig luithei into the exception il you want to ielay the uetails ol the valiuation eiiois.
Disabling Validate Before Save
You may want to exeit moie contiol ovei when valiuation occuis Ly calling the vaiious
valiuation methous explicitly in youi application. You can pievent Entity Fiamewoik
liom tiiggeiing the valiuation uuiing SaveChanges thanks to the DbContext.Configura
tion piopeity. One ol the settings you can conliguie on DbContext is ValidateOnSaveEn
abled. This is set to tiue Ly an inteinal methou when you instantiate a new DbCon
text, which means that it`s tiue Ly uelault on any DbContext class.
You can uisaLle it in the constiuctoi ol youi context class so that it`s always lalse
whenevei you instantiate a new instance ol the context.
Foi example, in BreakAwayContext you coulu auu the lollowing constiuctoi:
public class BreakAwayContext : DbContext
{
public BreakAwayContext()
{
Configuration.ValidateOnSaveEnabled = false;
}
... rest of class logic
}
You can also enaLle oi uisaLle this leatuie as neeueu thioughout youi application Ly
mouilying the conliguiation setting on youi context instance.
One Lenelit ol uisaLling the valiuation on SaveChanges anu calling the valiuation meth-
ous explicitly is that it allows you to avoiu having an exception thiown. Vhen valiua-
tions lail insiue ol the SaveChanges call, SaveChanges thiows the DbEntityValidationEx
ception. Howevei, as you`ve seen thiough this chaptei, calling GetValidationResult oi
GetValidationErrors explicitly ietuins something whethei the valiuations pass oi lail.
GetValidationResult ietuins a ValidationResult that will inuicate whethei oi not the
valiuation passeu. GetValidationErrors ietuins an IEnumerable ol ValidationResults
loi laileu valiuations anu il theie weie none, the IEnumerable will Le empty. Vhen
application peiloimance is an impoitant lactoi in youi uevelopment piocess, the ex-
pense ol exceptions might Le the ueciuing lactoi loi choosing the automatic valiuation
uuiing SaveChanges oi uisaLling that anu taking contiol ovei how anu when valiuation
occuis. You`ll leain moie aLout taking auvantage ol this conliguiation in the next
chaptei.
182 | Chapter 6:Validating with the Validation API
CHAPTER 7
Customizing Validations
In the pievious chaptei you leaineu many ways that you can apply valiuation iules so
that the DLContext Valiuation API can linu anu check them eithei on uemanu oi au-
tomatically. Vhile you can explicitly valiuate inuiviuual classes anu piopeities uiiectly
liom the DbEntityEntry methou, you can also have the context valiuate all ol its tiackeu
entities as a gioup, eithei Ly calling GetValidationErrors oi letting SaveChanges call that
methou loi you. GetValidationErrors then calls ValidateEntity on each ol the Added
anu Modified entities in the context. ValidateEntity then tiiggeis logic that checks the
ValidationAttribute anu IValidatableObject iules you`ve specilieu in youi classes.
You`ve seen how ValidateEntity woiks in Chaptei 6. In this chaptei, you`ll leain how
to customize the ValidateEntity methou not only Ly oveiiiuing the logic ol the methou,
Lut also Ly oveiiiuing the methou that ueteimines which entities shoulu Le valiuateu.
Overriding ValidateEntity in the DbContext
ValidateEntity is a viitual methou, meaning that you can oveiiiue it anu auu youi own
custom logic. Like any viitual methou, altei executing youi logic, you can contiol
whethei oi not it peiloims the valiuations it`s uesigneu to execute (loi example, vali-
uating the ValidationAttributes anu IValidatableObject iules).
Example 7-1 shows the ValidateEntity methou auueu into the BreakAwayContext class
altei using the Visual Stuuio IDE shoitcut to auu the oveiiiuuen methou.
Exanp|c 7-1. Signaturc oj \a|idatcEntity ovcrridc
protected override
System.Data.Entity.Validation.DbEntityValidationResult
ValidateEntity(
System.Data.Entity.Infrastructure.DbEntityEntry entityEntry,
System.Collections.Generic.IDictionary<object, object> items)
{
return base.ValidateEntity(entityEntry, items);
}
183
Il you aie new to oveiiiuing methous in Visual Stuuio, the IDE has a
shoitcut to help you inseit the methou in C= anu in VB. In the
BreakAwayContext class, type the woiu override (Overrides loi VB) lol-
loweu Ly a space. Visual Stuuio will then show you a list ol viitual
methous. Select ValidateEntity liom the list anu the methou coue will
Le automatically auueu to the class.
By ensuiing that the System.Data.Entity.Infrastructure, System.Data.Entity.Vali
dation, anu System.Collections.Generic namespaces aie all auueu to the using state-
ments at the top ol the class lile, the methou signatuie Lecomes a little easiei to ieau:
protected override DbEntityValidationResult ValidateEntity
(DbEntityEntry entityEntry,IDictionary<object, object> items)
{
return base.ValidateEntity(entityEntry, items);
}
You can auu logic to ValidateEntity that peiloims auuitional valiuations on all types
oi on a suLset ol types (loi example, a paiticulai type oi a paiticulai set ol types that
inheiit liom anothei class oi implement liom an inteilace).
Anothei Lenelit ol inseiting logic heie is that you have access to the DbContext anu
theieloie can peiloim valiuation that uepenus on othei tiackeu entities oi even checks
against uata in the uataLase. That`s something you can`t uo in a ValidationAttribute
oi in the IValidatableObject.Validate methou unless you weie to pass a DLContext
instance into the type. This woulu, howevei, loice the type to Le awaie ol the uata layei
which, il you caie aLout keeping youi POCO classes pcrsistcncc ignorant, is
unuesiiaLle.
You can ieau moie aLout peisistence ignoiance (PI) liom the peispec-
tive ol Entity Fiamewoik in Chaptei 2+ ol Progranning Entity Iranc-
wor|, 2c, oi in any numLei ol iesouices on the Inteinet. Heie, loi
example, is a uiscussion ol PI in the scope ol an aiticle on the Unit ol
Voik Pattein anu Peisistence Ignoiance Ly ]eiemy Millei in MSDN
Magazinc: http://nsdn.nicrosojt.con/cn-us/nagazinc/dd882510.aspx
=id0120053.
An example ol a valiuation that involves multiple entities is a iule loi BieakAway Geek
Auventuies that a payment must maue along with a new ieseivation. You`ll linu a
Payment class in the mouel ol the sample uownloau along with a Payments navigation
piopeity in the Reservation class. The two classes aie listeu in Example 7-2.
Exanp|c 7-2. Payncnt c|ass
public class Payment
{
public Payment()
{
184 | Chapter 7:Customizing Validations
PaymentDate = DateTime.Now;
}
public int PaymentId { get; set; }
public int ReservationId { get; set; }
public DateTime PaymentDate { get; set; }
public decimal Amount { get; set; }
}
public class Reservation
{
public Reservation()
{
Payments = new List<Payment>();
}
public int ReservationId { get; set; }
public DateTime DateTimeMade { get; set; }
public Person Traveler { get; set; }
public Trip Trip { get; set; }
public Nullable<DateTime> PaidInFull { get; set; }
public List<Payment> Payments { get; set; }
}
You may want youi custom context logic to take pieceuence ovei the othei valiuations
that woulu Le peiloimeu. In othei woius il the custom logic auueu in ValidateEntity
lails, then uon`t Lothei valiuating the iules that aie specilieu in ValidationAttributes
oi IValidatableObject. Il no eiiois aie uetecteu in the custom context logic, then the
base.ValidateEntity methou will get calleu to check iules uelineu with ValidationAt
tributes anu IValidatableObject. Figuie 7-1 helps you visualize this woikllow. You`ll
exploie a numLei ol othei possiLle woikllows latei in the chaptei.
Iigurc 7-1. Ca||ing basc va|idation on|y ij custon va|idation jinds no crrors
Overriding ValidateEntity in the DbContext | 185
The ValidateEntity signatuie contains an entityEntry paiametei. This iepiesents the
DbEntityEntry loi the oLject cuiiently Leing piocesseu Ly the SaveChanges methou.
DbEntityEntry allows you to navigate to the actual oLject instance that it iepiesents.
You cast with the as opeiatoi to ensuie you aie woiking with the coiiect type:
var reservation = entityEntry.Entity as Reservation;
if (reservation !=null)
{
//logic on reservation goes here
}
Fiom heie you can use the reservation instance oi woik uiiectly against the change
tiackei thiough entityEntry.
Example 7-3 shows coue that valiuates the new iule loi Reservation. The coue instan-
tiates a new DbEntityValidationResult loi this paiticulai entiy. Then, il the entiy is loi
a Reservation anu is new (Added) Lut has no Payments, a new eiioi is auueu to the
DbEntityValidationResult. Il the ieseivation valiuation iesults in eiiois (in which case,
result.IsValid will Le false), those iesults aie ietuineu liom ValidateEntity anu the
Lase valiuation is not calleu. Il the iesult is valiu, the Lase methou is calleu insteau.
RememLei liom Chaptei 6 that ValidateEntity tempoiaiily uisaLles
lazy loauing, so the context will not Le looking loi any payments in the
uataLase.
Exanp|c 7-3. \a|idatcEntity ca||ing basc va|idation on|y ij custon va|idation passcs
protected override DbEntityValidationResult ValidateEntity
(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = new DbEntityValidationResult(entityEntry,
new List<DbValidationError>());
var reservation = entityEntry.Entity as Reservation;
if (reservation != null)
{
if (entityEntry.State == EntityState.Added &&
reservation.Payments.Count == 0)
{
result.ValidationErrors.Add(
new DbValidationError(
"Reservation",
"New reservation must have a payment.")
);
}
}
if (!result.IsValid)
{
return result;
}
return base.ValidateEntity(entityEntry, items);
}
186 | Chapter 7:Customizing Validations
Keep in minu an impoitant uetail ol the piocessing steps uesciiLeu eai-
liei in Chaptei 6. GetValidationErrors (calleu Ly SaveChanges) will ex-
ecute ValidateEntity on all ol the tiackeu entities Leloie it Legins con-
stiucting commanus loi the uataLase. Vhen uesigning custom logic loi
ValidateEntity, uon`t expect entities that have alieauy Leen valiuateu
to Le in the uataLase Ly the time you ieach the next entity.
Il you have multiple valiuations to peiloim in ValidateEntity, it coulu get clutteieu up
pietty guickly. Example 7-+ shows the same logic as Example 7-3, Lut with the vali-
uation specilic to the Reservation split out to a sepaiate methou.
Exanp|c 7-1. \a|idatcEntity ca||ing basc va|idation on|y ij custon va|idation passcs
protected override DbEntityValidationResult ValidateEntity
(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = new DbEntityValidationResult(entityEntry,
new List<DbValidationError>());
ValidateReservation(result);
if (!result.IsValid)
{
return result;
}
//call base validation
return base.ValidateEntity(entityEntry, items);
}
private void ValidateReservation(DbEntityValidationResult result)
{
var reservation = result.Entry.Entity as Reservation;
if (reservation != null)
{
if (result.Entry.State == EntityState.Added &&
reservation.Payments.Count == 0)
{
result.ValidationErrors.Add(
new DbValidationError(
"Reservation",
"New reservation must have a payment.")
);
}
}
}
Considering Different Ways to Leverage ValidateEntity
In the pievious example, ValidateEntity executes oui context-Laseu Lusiness valiua-
tions. Il no eiiois aie lounu, it continues on to execute the Lase ValidateEntity methou,
Considering Different Ways to Leverage ValidateEntity | 187
which checks any iules uelineu with type valiuation (IValidatableObject iules) anu
piopeity valiuation (ValidateAttribute iules). That`s just one execution path you coulu
set up in ValidateEntity.
Thioughout this chaptei, we`ll piesent uilleient loims ol the Valida
teEntity methou. Il you aie lollowing along with the coue samples, you
might want to ietain each veision ol ValidateEntity in the BreakAway
Context class. Vhat we uiu while ueveloping oui samples was to wiap
a compliei uiiective aiounu the methous that we uon`t want to use any-
moie. This is cleanei than commenting out coue. In C= you can auu
#if false Leloie the Leginning ol the methou anu then #endif altei the
enu ol the methou.
#if false
protected override DbEntityValidationResult
ValidateEntity(DbEntityEntry entityEntry,
IDictionary<object, object> items)
{
...method code
}
#endif
The coue insiue the uiiective will Le giayeu out anu ignoieu Ly the
compilei. Change the uiiective to #if true to ieengage it.
In Visual Basic the uiiective looks like this:
#If False Then
#End If
You coulu ieveise this logic, ietuining the Lase ValidateEntity iesults liist anu, il theie
aie none, executing youi custom logic as visualizeu in Figuie 7-2.
Iigurc 7-2. Ca||ing custon va|idation on|y ij basc va|idation jinds on crrors
188 | Chapter 7:Customizing Validations
As an example, you might want to check a value loi unigueness in the uataLase, peihaps
to ensuie that new Lodgings have a unigue Name anu Destination comLination. You can
uo this in ValidateEntity Lecause you have access to the context anu theieloie can
execute a gueiy such as
Lodgings.Any(l => l.Name == lodging.Name &&
l.DestinationId == lodging.DestinationId);
But Lodging.Name alieauy has a numLei ol ValidationAttribute iules applieu: Required,
MinLength, anu MaxLength. You might pielei to ensuie that these thiee attiiLutes aie
satislieu Leloie wasting the tiip to the uataLase to check loi a uuplicate louging. You
coulu iun the Lase ValidateEntity methou liist anu ietuin its eiiois il theie aie any. Il
theie aie no eiiois lounu in the Lase valiuation, continue on to the new valiuation logic,
which checks the uataLase loi an existing louging with the name anu uestination ol the
one aLout to Le auueu. Example 7-5 uemonstiates this logic. Fiist, base.ValidateEn
tity is calleu. Il its iesults aie valiu, a custom valiuation methou, ValidateLodging, is
calleu anu its eiiois, il any, aie auueu to the iesults collection, which is ietuineu at the
enu.
Exanp|c 7-5. Exccuting contcxt va|idation on|y ij propcrty and typc va|idation pass
protected override DbEntityValidationResult ValidateEntity
(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
if (result.IsValid)
{
ValidateLodging(result);
}
return result;
}
private void ValidateLodging(DbEntityValidationResult result)
{
var lodging = result.Entry.Entity as Lodging;
if (lodging != null && lodging.DestinationId != 0)
{
if (Lodgings.Any(l => l.Name == lodging.Name &&
l.DestinationId == lodging.DestinationId))
{
result.ValidationErrors.Add(
new DbValidationError(
"Lodging",
"There is already a lodging named " + lodging.Name +
" at this destination.")
);
}
}
}
Considering Different Ways to Leverage ValidateEntity | 189
Checking loi unigueness in Example 7-5 may have maue you wonuei
aLout a simplei way to ueline unigue valiuations. The Entity Fiamewoik
team is woiking on a leatuie that woulu allow you to ueline Unigue
Constiaints uiiectly in the mouel. You can ieau moie uetails aLout this
in theii Maich 2011 Llog post at http://b|ogs.nsdn.con/b/cjdcsign/
archivc/2011/03/09/uniquc-constraints-in-thc-cntity-jrancwor|.aspx.
Ve cieateu a methou in the console app calleu CreateDuplicateLodging to test this
valiuation, shown in Example 7-6.
Exanp|c 7-. |nscrting Lodgings to tcst va|idations
private static void CreateDuplicateLodging()
{
using (var context = new BreakAwayContext())
{
var destination = context.Destinations
.FirstOrDefault(d => d.Name == "Grand Canyon");
try
{
context.Lodgings.Add(new Lodging
{
Destination = destination,
Name = "Grand Hotel"
});
context.SaveChanges();
Console.WriteLine("Save Successful");
}
catch (DbEntityValidationException ex)
{
Console.WriteLine("Save Failed: ");
foreach (var error in ex.EntityValidationErrors)
{
Console.WriteLine(
string.Join(Environment.NewLine,
error.ValidationErrors.Select(v => v.ErrorMessage)));
}
return;
}
}
}
The ciitical pait ol this methou inseits Gianu Hotel at the Gianu Canyon while the
Lulk ol the methou is coue to uisplay eiiois loi this uemonstiation. Oui seeu uata
incluues a Lodging calleu Gianu Hotel at the Destination Gianu Canyon. So oui new
Lodging will Le a uuplicate. Il you iun this methou liom the console application`s main
methou, ValidateEntity will call ValidateLodging anu uiscovei the uuplication. The
console will iepoit the eiioi:
190 | Chapter 7:Customizing Validations
Save Failed:
There is already a lodging named Grand Hotel at this destination.
Now let`s auu in a valiuation that will lail the base.ValidateEntity check. Mouily the
Louging to auu a uata annotation to the MilesFromNearestAirport piopeity. The
RangeAttribute specilies a valiu value iange loi the piopeity. Heie we`ll say that any-
thing liom .5 to 150 miles will Le valiu:
[Range(.5,150)]
public decimal MilesFromNearestAirport { get; set; }
Il you iun the application again, you`ll see this message in the console winuow:
Save Failed:
The field MilesFromNearestAirport must be between 0.5 and 150.
Theie`s no mention ol the uuplication. That`s Lecause the ValidateEntity methou is
uesigneu to check the piopeity anu type iules liist anu ietuin the exception iight away
il any aie lounuLeloie it has calleu ValidateLodging.
Let`s ietuin to the ValidateEntity methou anu loice it to ietuin the comLination ol
valiuation eiiois checkeu in the custom logic anu in the logic check Ly
base.ValidateEntity, as visualizeu in Figuie 7-3.
Iigurc 7-3. Conbining crrors jron custon and basc va|idation
Example 7-7 uemonstiates coue that will allow you to collect the iesults ol the Lase
valiuation anu then auu any auuitional eiiois lounu in the custom logic to that iesult
Leloie ietuining the comLineu eiiois liom ValidateEntity.
Exanp|c 7-7. Conbing typc and propcrty va|idation rcsu|ts with contcxt rcsu|ts
protected override DbEntityValidationResult ValidateEntity
(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
ValidateLodging(result);
return result;
}
Running the CreateDuplicateLodging methou one last time will now uisplay Loth
eiiois:
Save Failed:
The field MilesFromNearestAirport must be between 0.5 and 150.
There is already a lodging named Grand Hotel at this destination.
Considering Different Ways to Leverage ValidateEntity | 191
You can incluue multiple valiuation checks in ValiuateEntity. These ex-
amples only contain one at a time loi the sake ol Lievity.
Now that you`ve seen a lew possiLle woikllows loi executing valiuations in
ValidateEntity, you can mimic these oi ueline youi own woikllow when customizing
ValidateEntity.
Further Refactoring
As you implement this custom logic into youi own application, you may have many
custom valiuations uelineu in youi DLContext class. Rathei than having to call Vali
dateReservation, ValidateLodging, anu any otheis liom ValiuateEntity, you coulu
comLine them into a single methou such as IsValid anu call that liom ValidateEn
tity insteau. Foi example:
private void IsValid(DbEntityValidationResult result)
{
ValidateLodging(result);
ValidateReservation(result);
}
Updating Data During SaveChanges
Quite olten, theie aie last-minute mouilications that you want to make to uata Leloie
it`s sent to the uataLase. One example is setting DateAdded anu DateModified values in
youi classes. Vhile theie aie a numLei ol ways to achieve this in .NET coue, you may
wish to peiloim this logic in the uata layei. Because the context is alieauy iteiating
thiough all ol its Added anu Modified entities when it calls ValidateEntity, it`s tempting
to auu this logic into ValidateEntity iathei than peiloim an auuitional enumeiation
in the SaveChanges methou.
It`s possiLle to uo this, Lut it is not iecommenueu. The lollowing aie some uownsiues
to putting this type ol logic insiue ol ValiuateEntity:
The ValidateEntity methou is uesigneu loi peiloiming valiuations. Using it loi
othei puiposes inliinges on the piinciple ol Singulai ResponsiLilitya couing
piinciple that exists to help you in the guest loi maintainaLle coue.
You might not want to Lothei with mouilying uata Leloie you know it`s valiu anu
heaueu loi the uataLase. You coulu call base.ValidateEntity piioi to the upuate
logic as shown in Example 7-5, Lut a latei entity might Le invaliu, ienueiing all
mouilications moot.
By the time you`ie in the ValidateEntity methou, DetectChanges has alieauy Leen
calleu anu you neeu to Le caielul aLout how you upuate values.
192 | Chapter 7:Customizing Validations
One alteinative is to oveiiiue SaveChanges anu iteiate thiough entities to apply the uates
Leloie base.SaveChanges uoes a seconu iteiation to valiuate the entities. Keep in minu
that altei this, theie is a thiiu iteiationthe one that cieates anu executes the com-
manus loi each Added, Modified, anu Deleted entity to the uataLase.
Il you oveiiiue SaveChanges to apply the uate values, ValidateEntity will Le calleu al-
teiwaius, uuiing base.SaveChanges. Il invaliu uata is lounu, the elloit anu piocessing
time taken to upuate the uate piopeities was wasteu.
In the next section, we`ll look at pios anu cons ol the options you have to peiloim the
uate mouilications uuiing SaveChanges anu then show you an ellicient example ol
mouilying ModifiedDate anu AddedDate piopeities uuiing SaveChanges.
Overriding SaveChanges When Validation Occurs
Il you want to set values insiue ol SaveChanges anu you aie leveiaging the Valiuation
API, you have a numLei ol choices:
Upuate the uata values in SaveChanges anu let base.SaveChanges peiloim the val-
iuation (thiough ValidateEntity) as it noimally woulu.
Tuin oll ValidateOnSaveEnabled anu iteiate thiough entities, calling GetValidation
Result anu then the uate lix-up loi each entity.
Tuin oll ValidateOnSaveEnabled anu iteiate thiough entities, lixing up the uates
loi each entity anu then calling GetValidationResult.
Tuin oll ValidateOnSaveEnabled. Call GetValidationErrors (which will iteiate
thiough entities) anu then iteiate again peiloiming the uate lix-ups.
Tuin oll ValidateOnSaveEnabled. Iteiate thiough entities to peiloim the uate lix-
ups, anu then call GetValidationErrors (which will iteiate thiough entities). This
woulu Le no uilleient than the liist option in this list.
Theie aie pios anu cons to each appioach. Ve`ll walk thiough one ol them anu piesent
the pios anu cons ol the otheis. In the enu, you shoulu Le aLle to choose the appioach
that Lest lits youi application.
Foi this example, we`ll peiloim valiuations Ly calling GetValidationErrors anu then
upuate two uate lielus loi any entity that inheiits a new Lase class, Logger. In this
scenaiio, we have conliuence that upuating the uate lielus won`t Lieak any valiuation
iules, so it is sale to peiloim this task altei the valiuations have Leen checkeu Ly the
API. In a ieal-woilu application, you shoulu have automateu tests in place to ensuie
that lutuie mouilications to the application uon`t Lieak this asseition.
Example 7-S shows a new aLstiact class calleu Logger, which exposes two new uate
lielus. It also has a puLlic methou, UpdateModificationLogValues, loi upuating those
lielus. This methou may Le useu Ly any numLei ol Lusiness logic methous that access
youi classes.
Overriding SaveChanges When Validation Occurs | 193
Exanp|c 7-8. Loggcr basc c|ass
public abstract class Logger
{
public DateTime LastModifiedDate { get; set; }
public DateTime AddedDate { get; set; }
public void UpdateModificationLogValues(bool isAdded)
{
if (isAdded)
{
AddedDate = DateTime.Now;
}
LastModifiedDate = DateTime.Now;
}
}
Mouily the Activity class to inheiit liom Logger:
public class Activity : Logger
Now you can oveiiiue SaveChanges, as shown in Example 7-9. Back in Chaptei 5 you
oveiioue SaveChanges to peiloim logging; you shoulu ieplace the logging implemen-
tation with this new implementation. Notice that the liist action in the methou is to
stoie the cuiient setting ol AutoDetectChangesEnabled. That`s Lecause we`ie going to
tempoiaiily set it to false anu want to ieset it Leloie exiting the methou. The ieason
we`ie setting it to false is to contiol exactly when DetectChanges is calleu.
Exanp|c 7-9. SavcChangcs ovcrriddcn to pcrjorn va|idation and updatcs
public override int SaveChanges()
{
var autoDetectChanges = Configuration.AutoDetectChangesEnabled;
try
{
Configuration.AutoDetectChangesEnabled = false;
ChangeTracker.DetectChanges();
var errors = GetValidationErrors().ToList();
if (errors.Any())
{
throw new DbEntityValidationException
("Validation errors found during save.", errors);
}
foreach (var entity in this.ChangeTracker.Entries()
.Where(e =>
e.State ==EntityState.Added ||
e.State == EntityState.Modified))
{
ApplyLoggingData(entity);
}
ChangeTracker.DetectChanges();
Configuration.ValidateOnSaveEnabled = false;
194 | Chapter 7:Customizing Validations
return base.SaveChanges();
}
finally
{
Configuration.AutoDetectChangesEnabled = autoDetectChanges;
}
}
The ApplyLoggingData methou shown in Example 7-10 will call UpdateModification
LogValues on any entities that inheiit liom Logger, passing in a Boolean to signal
whethei oi not the AddedDate neeus to Le set. Once all the uates have Leen upuateu,
we call DetectChanges to ensuie that Entity Fiamewoik is awaie ol any changes that
weie maue.
Exanp|c 7-10. App|yLoggingData ncthod in Brca|AwayContcxt c|ass
private static void ApplyLoggingData(DbEntityEntry entityEntry)
{
var logger = entityEntry.Entity as Logger;
if (logger == null) return;
logger.UpdateModificationLogValues
(entityEntry.State == EntityState.Added);
}
Now we`ll test that the uate lielus get changeu Ly auuing a new, valiu Activity using
the InsertActivity methou (auueu into the console application) shown in Exam-
ple 7-11. The coue uisplays the uates altei SaveChanges has Leen calleu. Altei the liist
call to SaveChanges, the coue makes a mouilication anu saves again, uisplaying the uates
a seconu time.
Exanp|c 7-11. Conso|c ncthod to obscrvc Loggcr propcrtics updatcd in SavcChangcs
private static void InsertActivity()
{
var activity = new Activity { Name = "X-C Skiing" };
using (var context = new BreakAwayContext())
{
context.Activities.Add(activity);
try
{
context.SaveChanges();
Console.WriteLine("After Insert: Added={0}, Modified={1}",
activity.AddedDate, activity.LastModifiedDate);
//pause 2 seconds
System.Threading.Thread.Sleep(2000);
activity.Name = ("X-C Skating");
context.SaveChanges();
Console.WriteLine("After Modified: Added={0}, Modified={1}",
activity.AddedDate, activity.LastModifiedDate);
}
catch (DbEntityValidationException ex)
{
Console.WriteLine("Save Test Failed: " +
Overriding SaveChanges When Validation Occurs | 195
ex.EntityValidationErrors.FirstOrDefault()
.ValidationErrors.First().ErrorMessage);
}
}
}
Vhen you iun this, you`ll see that the newly inseiteu activity has its AddedDate anu
LastModifiedDate values populateu altei Leing saveu. Then when the activity is euiteu
anu saveu again, you can see that its LastModifiedDate value has Leen upuateu again,
thanks to the comLineu logic in SaveChanges anu the Logger class:
After Insert: Added=12/9/2011 12:16:27 PM, Modified=12/9/2011 12:16:27 PM
After Modified: Added=12/9/2011 12:16:27 PM, Modified=12/9/2011 12:16:33 PM
This woiks Lecause Ly the time we`ie calling ApplyLoggingData, the context is alieauy
awaie that these aie eithei Added oi Modified entities anu is alieauy planning to peisist
them to the uataLase.
You can avoiu the neeu to call DetectChanges Ly changing piopeities uiiectly thiough
the change tiackei. That`s logic that you won`t Le aLle to (oi want to) emLeu into youi
uomain classes (which we pielei to not have any knowleuge ol Entity Fiamewoik). So
you`ll have to uo that within the context. Example 7-12 shows what the ApplyLogging
Data woulu look like il you weie to set the piopeities thiough the change tiackei.
Exanp|c 7-12. Using thc changc trac|cr to updatc sca|ar propcrtics
private static void ApplyLoggingData(DbEntityEntry entityEntry)
{
var logger = entityEntry.Entity as Logger;
if (logger == null) return;
entityEntry.Cast<Logger>()
.Property(l => l.ModifiedDate).CurrentValue = DateTime.Now;
if (entityEntry.State==EntityState.Added)
{
entityEntry.Cast<Logger>()
.Property(l => l.AddedDate).CurrentValue = DateTime.Now;
}
}
In Chaptei 5, you leaineu how to woik with scalais as well as collection anu ieleience
navigation piopeities thiough the change tiackei. Il you want to make changes to nav-
igation piopeities at the uata layei, you shoulu uo so using the change tiackei, as shown
with the scalai piopeity changes maue in Example 7-12. Il you useu the navigation
piopeities ol the class uiiectly, whethei you uo that in the context coue oi call into coue
in the type you aie mouilying (loi example, Logger.UpdateModificationLogValues), you
iun a suLstantial iisk ol those changes not Leing peisisteu to the uataLase. Again, this
is uepenuent on wheie in the woikllow DetectChanges is Leing calleu. Il you aie in the
haLit ol using the change tiackei to make the changes, you uon`t have to woiiy aLout
DetectChanges.
196 | Chapter 7:Customizing Validations
Comparing ValidateEntity to SaveChanges for Custom Logic
Il you`ve Leen using Entity Fiamewoik loi a lew yeais, you might Le lamiliai with
vaiious options we`ve hau loi applying valiuation logic anu wonueiing how
ValidateEntity lits into the pictuie. The liist veision ol Entity Fiamewoik gave us the
ObjectContext.SavingChanges event, which let uevelopeis execute valiuation oi othei
logic when SaveChanges was calleu. Youi logic auueu into SavingChanges woulu Le ex-
ecuteu anu then Entity Fiamewoik woulu execute its inteinal logic. Entity Fiamewoik
+ Liought us the auueu Lenelit ol a viitual SaveChanges methou so we coulu not only
have Entity Fiamewoik execute oui custom logic when calling SaveChanges, Lut we hau
the option ol completely halting the inteinal coue.
You can also oveiiiue SaveChanges when it is calleu liom DbContext. DbContext uoesn`t
have a SavingChanges event Lecause with the viitual SaveChanges, the loimei appioach
is ieuunuant. The only ieason SavingChanges still exists as a methou ol ObjectContext
is loi Lackwaiu compatiLility. But il you neeu to, you can get to ObjectContext liom
DbContext Ly liist uiopping uown to the ObjectContext using the IObjectContextAdap
ter, as you`ve seen pieviously in this Look.
ValidateEntity is yet anothei extensiLility point that is availaLle uuiing SaveChanges.
But as you`ve seen in this chaptei, you shoulu Le consiueiate ol when youi coue makes
use ol ValidateEntity oi SaveChanges to inseit youi logic.
ValidateEntity Ly uelault is executeu on eveiy Added oi Modified oLject Leing tiackeu.
It is a goou ieplacement loi coue that you may have put in SaveChanges wheie you iteiate
thiough each tiackeu oLject anu peiloim some valiuation logic on it.
A Lig caveat with the ValidateEntity methou, howevei, is that it is executeu altei
DetectChanges has Leen calleu, so you have to Le caielul aLout how you go aLout setting
piopeities. You can salely set piopeities using the DbEntityEntry, Lut oui pieleience is
to avoiu auuing nonvaliuation logic into a methou that is uesignateu loi peiloiming
valiuations.
The SaveChanges methou is a goou place to execute logic wheie you want to uo some-
thing with a gioup ol oLjects. Foi example, you might want to log how many ieseiva-
tions aie auueu in a paiticulai upuate. Vhile you uo have access to this in the
ValidateEntity methou, this is something you want to execute only once uuiing a save.
Miciosolt`s guiuance is to use ValidateEntity to peiloim valiuation logic (iule check-
ing) only. Theii piimaiy ieason loi this guiuance is concein ovei incoiiectly coueu
piopeity mouilications that won`t get pickeu up Ly the context il the uevelopei is un-
awaie ol the lact that DetectChanges was alieauy calleuanu will not Le calleu again.
Anothei is that in ValidateEntity, the team has ensuieu that lazy loauing won`t have
unexpecteu ellects on the valiuation.
Fiom a peispective ol aichitectuial guiuance, yet anothei ieason is that Ly not loicing
ValidateEntity to peiloim non-valiuation logic, you lollow the piinciple ol Single Re-
sponsiLility. ValidateEntity is loi valiuating. Single ResponsiLility helps to keep in
Overriding SaveChanges When Validation Occurs | 197
minu the lact that il you intiouuce othei leatuies into that methou, you`ll inciease the
uilliculty ol maintaining youi application as it giows anu evolves.
Using the IDictionary Parameter of ValidateEntity
So lai we`ve locuseu on the entityEntry paiametei ol ValidateEntity. Theie is also an
IDictionary<object, object> paiametei availaLle:
protected override DbEntityValidationResult ValidateEntity
(DbEntityEntry entityEntry, IDictionary<object, object> items)
By uelault, the value ol this paiametei is null, Lut you can use the paiametei to pass
auuitional values to custom implementations ol IValidatableObject.Validate oi Vali
dationAttributes.
Vatch loi a change to the IDictionary paiametei in a lutuie veision ol
Entity Fiamewoik: it may Le changeu to uelault to an empty uictionaiy
iathei than null. That woulu make couing against it simplei. As ol Entity
Fiamewoik +.3, the paiametei is still null.
Foi example, iecall the signatuie ol IValidatableObject.Validate:
public IEnumerable<ValidationResult>
Validate(ValidationContext validationContext)
ValidationContext implements iDictionary. Entity Fiamewoik passes the items ue-
lineu in ValidateEntity to this validationContext paiametei. It`s also possiLle to use
a ValidationContext when cieating oveiiiues ol the ValidationAttribute class. (See the
MSDN LiLiaiy uocumentation topic ValidationAttribute Class loi moie inloimation
aLout this leatuie: http://nsdn.nicrosojt.con/cn-us/|ibrary/systcn.conponcntnodc|.da
taannotations.va|idationattributc.aspx)
You can cieate a uictionaiy ol oLjects in the ValidateEntity methou anu pass them
along in the Lase ValidateEntity call Ly assigning the uictionaiy to the items vaiiaLle.
Those oLjects woulu then Le availaLle loi you to use in valiuations that accept a Vali
dationContext.
Foi example, you may want to Le suie that a newly auueu oi mouilieu payment uoes
not cause the saveu payments to exceeu the cost ol the tiip on which the ieseivation is
Lookeu. To valiuate this iule, you woulu neeu the uata layei to access the uataLase
when it`s valiuating new oi mouilieu payment oLjects. But iathei than peiloiming all
ol the calculations in the uata layei, you coulu have the uata layei pioviue the necessaiy
inloimation to the payment so that the iule can Le incluueu in the Lusiness logic loi
the Payment itsell.
198 | Chapter 7:Customizing Validations
I`ve seen examples wheie the DbContext itsell is passeu Lack into the
ValidationContext ol IValidatable.Validate oi ValidationAttributes
liom ValidateEntity. Neithei ol us aie lans ol this pattein Lecause it
loices the oLject to Le awaie ol the context, ol the uata layei, anu ol
Entity Fiamewoik. Not only is the class no longei POCO, Lut it also
iemoves anothei guality that I have leaineu to aumiie, peisistence ig-
noiance.
Example 7-13 shows the Validate methou loi Payment altei we`ve mouilieu Payment to
implement the IValidatableObject inteilace. Theie aie two valiuations in the methou.
The methou liist checks to see il theie aie PaymentSum anu TripCost items in the vali
dationContext. The methou expects that the methou that has tiiggeieu Valiuate will
have cieateu these items in the uictionaiy passeu in as the validationContext paiametei.
Il they aie theie, the methou will use those to compaie the payments to the tiip cost.
Exanp|c 7-13. |\a|idatab|cObjcct.\a|idatc ncthod using \a|idationRcsu|t
public IEnumerable<ValidationResult> Validate(
ValidationContext validationContext)
{
var vc = validationContext; //for book readability
if (vc.Items.ContainsKey("DbPaymentTotal")
&& vc.Items.ContainsKey("TripCost"))
{
if (Convert.ToDecimal(vc.Items["DbPaymentTotal"]) + Amount >
Convert.ToDecimal(vc.Items["TripCost"]))
{
yield return new ValidationResult(
"Oh horrors! The client has overpaid!",
new[] { "Reservation" });
}
}
}
This example is to uemonstiate the use ol the IDictionary anu not meant
as the ue lacto pattein loi checking loi oveipayments in a piouuction
application. Theie aie many moie lactois to take into consiueiation loi
this paiticulai use case iegaiuless ol whethei you aie using Entity
Fiamewoik valiuation oi any othei valiuation pattein.
Given that the Payment class valiuation expects to Le pioviueu with a ValidationCon
text that supplies the sum ol Payments loi a single Reservation that aie alieauy in the
uataLase anu the cost ol the tiip loi which the payment anu ieseivation aie maue, the
ValidateEntity methou neeus to auu those values into the IDictionary.
Example 7-1+ uoes just thatietiieving the total ol payments loi the Reservation anu
the TripCost liom the uataLase, anu then auuing them to the _items IDictionary. This
Using the IDictionary Parameter of ValidateEntity | 199
example also places the paiticulai valiuation logic, FillPaymentValidationItems, in a
sepaiate methou in oiuei to keep the ValidateEntity methou cleanei.
Exanp|c 7-11. \a|idatcEntity passing va|ucs using thc |Dictionary
protected override DbEntityValidationResult ValidateEntity
(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var _items = new Dictionary<object, object>();
FillPaymentValidationItems(entityEntry.Entity as Payment, _items);
return base.ValidateEntity(entityEntry, _items);
}
private void FillPaymentValidationItems(Payment payment, Dictionary<object, object> _items)
{
if (payment == null)
{
return;
}
//calculate payments already in the database
if (payment.ReservationId > 0)
{
var paymentData = Reservations
.Where(r => r.ReservationId == payment.ReservationId)
.Select(r => new
{
DbPaymentTotal = r.Payments.Sum(p => p.Amount),
TripCost = r.Trip.CostUSD
}).FirstOrDefault();
_items.Add("DbPaymentTotal", paymentData.DbPaymentTotal);
_items.Add("TripCost", paymentData.TripCost);
}
}
Notice that ValidateEntity uoesn`t check the type ol the entityEntry.Entity. Insteau
it leveiages the peiloimance Lenelit ol casting with as, which will ietuin a null il the
entity is not a Payment. Then the helpei methou uoes a guick check loi a null Leloie
Lotheiing with the innei logic. This is simply a uesign uecision maue on oui pait.
The methou liist ensuies that the ReservationId has Leen set. Il the usei is auuing a
new Reseivation anu Payment togethei, then the Reservation won`t have a
ReservationId yet anu theieloie it won`t have Leen set on the Payment.
Controlling Which Entities Are Validated in ValidateEntity
As we`ve pointeu out eailiei, Ly uelault, Entity Fiamewoik will only senu Added anu
Modified entities to the ValidateEntity methou. Inteinal coue checks the state Leloie
calling ValidateEntity in a virtual (Overridable in VB) methou ol DbContext calleu
ShouldValidateEntity.
200 | Chapter 7:Customizing Validations
Altei some inteinal evaluation, ShouldValidateEntity ietuins a Boolean Laseu on this
line ol coue which checks to see il the state is eithei Modified oi Added:
return ((entityEntry.State &
(EntityState.Modified | EntityState.Added)) != 0);
Because ShouldValidateEntity is viitual, you can oveiiiue the uelault logic anu specily
youi own iules loi which entities aie valiuateu. You may want only ceitain types to Le
valiuateu oi you may want valiuation peiloimeu on Deleted oLjects.
As an example, it woulun`t make sense to uelete Reservations loi Trips that aie in the
past. Il you want to captuie ueleteu Reservations in the uata layei anu check to make
suie they aien`t loi past Trips, you`ll have to make suie the ueleteu Reservation makes
it to the ValidateEntity methou. You uon`t have to senu all ueleteu oLjects. Insteau
you can open up the pipeline only loi ueleteu Reservations.
Auu the ShouldValidateEntity methou to the BreakAwayContext. You can use the same
steps to oveiiiue the methou explaineu in the eailiei note when auuing the ValidateEn
tity methou.
Example 7-15 shows the ShouldValidateEntity methou altei we`ve auueu auuitional
logic to allow ueleteu Reservation oLjects to Le valiuateu as well. Il the entity Leing
evaluateu is a ueleteu Reservation, ShouldValidateEntity will ietuin true. Il not, it will
peiloim its uelault logic to ueteimine whethei oi not to valiuate the entity.
Exanp|c 7-15. Ovcrriding Shou|d\a|idatcEntity
protected override bool ShouldValidateEntity(DbEntityEntry entityEntry)
{
return base.ShouldValidateEntity(entityEntry)
|| ( entityEntry.State == EntityState.Deleted
&& entityEntry.Entity is Reservation);
}
Once you`ve alloweu the entiy to pass thiough to ValidateEntity, you`ll neeu to auu
logic to ValidateEntity to peiloim the new valiuation.
Controlling Which Entities Are Validated in ValidateEntity | 201
CHAPTER 8
Using DbContext in
Advanced Scenarios
The locus ol this Look so lai has Leen to get you up anu iunning with using the
DbContext, along with its paitnei APIsValiuation anu Change Tiacking. Now it`s
time to look at some auvanceu anu less commonly useu leatuies ol DbContext, the
DbSet anu the Database classes, as well as moving Letween a DbContext anu ObjectCon
text. Even though this Look is not an application patteins Look, we will also take a
look at two inteiesting application scenaiios. One will Le a uiscussion ol uelining youi
DbContext anu taking into consiueiation the use ol multiple contexts in youi application
to taiget only the sets ol mouel classes that aie neeueu in any given scenaiio. The othei
will Le a look at leveiaging the IDbSet to cieate aLstiactions that will allow you to Luilu
moie llexiLle applications. In Progranning Entity Irancwor|, 2c, you`ll linu an ex-
tensive sample that uses ObjectSet, automateu unit testing, anu iepositoiies. This IDb
Set example will Le a slice ol that, explaining how you can ieplicate the pattein using
the DLContext API.
Moving Between ObjectContext and DbContext
As you`ve leaineu, the DbContext is a smallei API exposing the most commonly useu
leatuies ol the ObjectContext. In some cases, those leatuies aie miiioieu in the DLCon-
text API. In othei cases, the Entity Fiamewoik team has simplilieu moie complex cou-
ing Ly pioviuing us with methous like Find oi piopeities like DbSet.Local. But theie`s
a Lig API luiking unueineath that you may still neeu access to. Foi example, you might
want to woik uiiectly with the MetadataWorkspace to wiite geneiic coue against classes
Lecause that API can ieau the mouel moie elliciently than iellection. Auuitionally, the
MetadataWorkspace is aLle to pioviue moie inloimation aLout the metauata than you
can uiscovei with iellection, loi example, loi Key piopeities. Oi you might want to
take auvantage ol a uataLase-specilic lunction that is exposeu thiough Entity SQL,
which you can`t access liom LINQ to Entities. Oi you may alieauy have an application
203
wiitten using the ObjectContext anu you want to leveiage the DbContext in lutuie up-
uates without ieplacing all ol the ObjectContext coue.
All ol these scenaiios aie achievaLle.
You can leain aLout MetadataWorkspace, mentioneu aLove, in Chaptei
1S ol Progranning Entity Irancwor|, 2c.
Accessing ObjectContext Features from a DbContext
Il you aie staiting with a DbContext, you can access the ObjectContext leatuies veiy
easily thiough the IObjectContextAdapter. In lact, you`ve seen this uone a lew times in
this Look alieauy. In Chaptei + we useu this to access the ObjectMaterialized event
that is not availaLle uiiectly on DbContext.
The Entity Fiamewoik team ieleis to the pioceuuie ol accessing the
ObjectContext liom DbContext as uiopping uown to ObjectContext.
You have seen this expiession useu a lew times alieauy in this Look.
The pattein to get to the ObjectContext is to cast the DbContext instance to this IOb
jectContextAdapter anu, liom theie, access its ObjectContext piopeity. DbContext is
implementeu as an explicit inteilace ol IObjectContextAdapter, which is why you neeu
to explicitly cast it:
((IObjectContextAdapter)context).ObjectContext
Once you have the ObjectContext in hanu, you can woik uiiectly against that. This is
not a new instance. DbContext wiaps ObjectContext; the ObjectContext instance ie-
tuineu liom IObjectContextAdapter is the instance that youi DbContext was alieauy
using inteinally.
Il you aie wiiting a layeieu application anu uon`t want othei uevelopeis on youi team
to woiiy aLout this implementation uetail, you coulu cieate a piopeity to allow them
to get uiiectly liom the DbContext to the unueilying ObjectContext.
Foi example, they may Le awaie that theie aie moie auvanceu leatuies availaLle when
they neeu them. You coulu wiap those into a piopeity calleu Core. Heie`s an example
ol a Core piopeity that casts with the as opeiatoi:
public ObjectContext Core
{
get
{
return (this as IObjectContextAdapter).ObjectContext;
}
}
204 | Chapter 8:Using DbContext in Advanced Scenarios
Now you can simply call Core liom the DbContext instance to get at the uesiieu leatuies.
Adding DbContext into Existing .NET 4 Applications
Vhat il you have an existing .NET + application that uses ObjectContext, Lut now you
aie extenuing the leatuies ol the application anu woulu like to take auvantage ol the
DbContext loi new coue? You can uo this thanks to one ol the oveiloaus loi instantiating
a new DbContext. The oveiloau allows you to pass in an existing ObjectContext instance.
The oveiloau takes two paiameteis. The liist is an ObjectContext instance anu the
seconu is a Boolean inuicating whethei oi not the DbContext can uispose ol the Object
Context when the DbContext is uisposeu:
public DbContext(ObjectContext objectContext,
bool dbContextOwnsObjectContext)
{}
Il you weie to call this oveiloau uiiectly liom the coue that uses this context, that coue
woulu neeu to pioviue an ObjectContext instance each time as well as the bool value.
Rathei than loice this onto the uevelopei who is consuming the context, you can cieate
a DbContext class that will uo this automatically as well as expose the DbSets necessaiy
loi gueiying, upuating, change tiacking etc.
Foi the sake ol uemonstiating, we`ll stait with a sample uownloau liom Progranning
Entity Irancwor|, 2c. The VPF application liom Chaptei 26 uses a uataLase-liist
EDMX with geneiateu POCO classes anu a hanu-Luilt ObjectContext class. This mouel
is Laseu on the BieakAway uomain, as is the mouel we`ve useu thioughout this Look.
The ObjectContext class, BAEntities, exposes a numLei ol ObjectSets. Example S-1
uisplays a suLset ol its coue listing.
Exanp|c 8-1. A portion oj thc origina| BAEntitics ObjcctContcxt c|ass
public partial class BAEntities : ObjectContext
{
public const string ConnectionString = "name=BAEntities";
public const string ContainerName = "BAEntities";
public BAEntities()
: base(ConnectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = false;
Initialize();
}
partial void Initialize();
public ObjectSet<Activity> Activities
{
get { return _activities ??
(_activities = CreateObjectSet<Activity>("Activities")); }
}
Moving Between ObjectContext and DbContext | 205
private ObjectSet<Activity> _activities;
public ObjectSet<Contact> Contacts
{
get { return _contacts ??
(_contacts = CreateObjectSet<Contact>("Contacts")); }
}
private ObjectSet<Contact> _contacts;
public ObjectSet<Trip> Trips
{
get { return _trips ??
(_trips = CreateObjectSet<Trip>("Trips")); }
}
private ObjectSet<Trip> _trips;
public ObjectSet<Destination> Destinations
{
get { return _destinations ??
(_destinations =
CreateObjectSet<Destination>("Destinations")); }
}
private ObjectSet<Destination> _destinations;
}
In the pioject that contains this BAEntities class, we`ve auueu the EntityFiamewoik
package ieleience anu a new class lile, BAEntiticsDbContcxt.cs, that contains the BAEn
titiesDbContext class. This new class uoes thiee impoitant things:
1. Inheiits liom DbContext
2. Has a uelault constiuctoi that calls a piivate constiuctoi with the ObjectContext
oveiloau.
3. Exposes DbSets to coue against using the DLContext API.
Example S-2 shows the listing loi BaEntitiesDbContext. It incluues DbSet piopeities to
expose the classes exposeu Ly the ObjectSet piopeities shown in Example S-1. Theie
aie othei DbSet piopeities in the class Lut they aie not ielevant to this example.
Exanp|c 8-2. DbContcxt c|ass that wraps thc BAEntitics ObjcctContcxt
using System.Data.Entity;
using System.Data.Objects;
namespace BAGA
{
public class BAEntitiesDbContext: DbContext
{
public BAEntitiesDbContext():this(new BAEntities(),
dbContextOwnsObjectContext:true)
{
}
public DbSet<Activity> Activities{get;set;}
public DbSet<Contact> Contacts {get;set;}
206 | Chapter 8:Using DbContext in Advanced Scenarios
public DbSet<Trip> Trips {get;set;}
public DbSet<Destination> Destinations {get;set;}
}
}
The constiuctoi is a uelault constiuctoi that takes no paiameteis, Lut its ueclaiation
invokes the Lase DbContext constiuctoi oveiloau that uses an ObjectContext.
The automateu tests listeu in Example S-3 veiily that, using this DbContext, we can
ietiieve, inseit, anu euit entities. Notice that the thiiu test uses the DbSet.Find methou
as well. Because the uataLase geneiates new key values loi Trip, the seconu test veiilies
that the inseiteu Trip has a TripId gieatei than 0. The thiiu test uses a similai asseition
loi the inseiteu Payment. The thiiu test also ie-ietiieves the ieseivation liom the uata-
Lase to check that its ModifiedDate value was upuateu.
Exanp|c 8-3. Autonatcd tcsts to cxcrcisc thc DbContcxt that wraps an cxisting ObjcctContcxt
using System.Linq;
using System.Transactions;
using BAGA;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace DbContextTests
{
[TestMethod]
public void CanRetrieveTripViaDbContext()
{
using (var context = new BAEntitiesDbContext())
{
Assert.IsNotNull(context.Trips.FirstOrDefault());
}
}
[TestMethod]
public void CanInsertTripViaDbContext()
{
using (new TransactionScope())
{
var trip = new Trip
{
DestinationID = 55,
LodgingID = 1,
StartDate = new DateTime(2012, 1, 1),
EndDate = new DateTime(2012, 2, 1),
TripCostUSD = 1000
};
using (var context = new BAEntitiesDbContext())
{
context.Trips.Add(trip);
context.SaveChanges();
Assert.IsTrue(trip.TripID > 0);
}
}
Moving Between ObjectContext and DbContext | 207
}
[TestMethod]
public void CanRetrieveandModifyReservationandAddPayment()
{
using (new TransactionScope())
{
DateTime reservationDate;
using (var context = new BAEntitiesDbContext())
{
//4 is a known reservation in the database
var res = context.Reservations.Find(4);
reservationDate = res.ReservationDate.AddDays(-1);
res.ReservationDate = reservationDate;
var payment = new Payment
{
Amount = 100,
ModifiedDate = DateTime.Now,
PaymentDate = DateTime.Now.Date
};
res.Payments.Add(payment);
context.SaveChanges();
Assert.IsTrue(payment.PaymentID > 0);
}
using (var context = new BAEntitiesDbContext())
{
Assert.AreEqual(reservationDate,
context.Reservations.Find(4).ReservationDate);
}
}
}
}
}
Il you want to take auvantage ol DbContext when auuing new leatuies to an existing
application that uses an ObjectContext, you can uo so with the auuition ol a DbCon
text in the style ol the BaEntitiesDbContext. Be suie to test youi logic!
Leveraging SQL Server Operators Exposed in SqlFunctions
One scenaiio that we`ve Leen askeu aLout iecently was the aLility to uetect numeiic
uata in some type ol stiing column in the uataLase. SQL Seivei has an IsNumeric lunc-
tion Lut theie`s no way to expiess that in LINQ to Entities. A set ol SQL Seivei specilic
lunctions was wiappeu into System.Data.Objects.SQLClient.SqlFunctions in .NET+.
These can Le useu in LINQ to Entities gueiies against the ObjectContext anu against
DbContext.
In the BieakAway uomain, peihaps you neeu to seaich loi numeiic zip coues. Because
postal coues can Le alphanumeiic in many paits ol the woilu, the zip coue lielu is a
stiing anu in the uataLase, it`s an nvarchar.
208 | Chapter 8:Using DbContext in Advanced Scenarios
To use IsNumeric in youi gueiy, you`ll neeu a using statement loi the Sys
tem.Data.Objects.SqlClient namespace at the top ol youi coue lile.
Then you can use SqlFunctions uiiectly in the gueiy expiession. Example S-+ uemon-
stiates using the IsNumeric lunction to ietuin a list ol people with numeiic zip coues.
IsNumeric ietuins 1 loi valiu numLeis, which is why the gueiy seaiches loi IsNumeric
is egual to 1.
Exanp|c 8-1. Using SQL Scrvcr |sNuncric in L|NQ to Entitics
private static void UseSqlFunctions()
{
using (var context = new BreakAwayContext())
{
var query=from p in context.People
where SqlFunctions.IsNumeric(p.LastName)==1
select p;
var results=query.ToList();
}
}
Il you look at the gueiy in a piolilei, you can see that the IsNumeric lunction is useu in
the SQL gueiy. Heie aie the last two lines ol the SQL executeu in the uataLase:
FROM [dbo].[People] AS [Extent1]
WHERE 1 = (ISNUMERIC([Extent1].[ZipCode]))
]ust Le suie to keep in minu that this is specilically uesigneu loi use in SQL Seivei
uataLases, though othei pioviueis coulu make similai lunctionality availaLle loi theii
uataLases.
Querying Derived Types with DbSet
Il you have Leen woiking with ObjectContext anu ObjectSet, you shoulu Le awaie ol
anothei Lenelit ol DbSet when woiking with ueiiveu types. You can cieate DbSets that
encapsulate ueiiveu types. Vhen woiking with ObjectSet, you aie only aLle to cieate
sets liom a Lase type. So il you hau a hieiaichy such as Person with a ueiiveu type,
Customer, any time you wanteu to gueiy Customer you woulu have to expiess a gueiy
staiting with
context.People.OfType<Customer>()
This can get pietty teuious.
The DLContext API lets you cieate DbSet piopeities liom ueiiveu types without having
to ueclaie the Lase type oi set the OfType methou to access the ueiiveu type. Il you want
to expose the ueiiveu type as a DbSet, you simply auu it as you woulu any othei entity
in the mouel:
public DbSet<Customer> Customers { get; set; }
Querying Derived Types with DbSet | 209
You can expose DbSet piopeities loi Lase types anu theii ueiiveu types in youi context.
As with ObjectSet, gueiying a DbSet ol the Lase type (loi example, DbSet<Person>) will
ietuin all ol the types in the hieiaichy, incluuing ueiiveu types that aie exposeu Ly theii
own DbSet piopeity.
Understanding the Interface Property Limitation
Entity Fiamewoik is unaLle to cieate schema liom inteilaces. That means il you have
any piopeities (complex type piopeities oi navigation piopeities) that aie inteilace
types, Coue Fiist will not Luilu those into the mouel. Youi coue may iun without
thiowing an exception, Lut the uataLase won`t have any schema to iepiesent that type
anu theieloie its uata will not Le peisisteu.
Foi example, you might have an IDestination inteilace anu the Destination class anu
some othei classes implementing that inteilace:
public class Destination : IDestination
public class EliteDestination : IDestination
public class RoughingItDestination : IDestination
Then consiuei the Destination piopeity in Lodging class. Rathei than always ietuining
a Destination instance, you might want to ietuin any ol those types:
[Required]
public IDestination Destination { get; set; }
Unloitunately, this scenaiio is just not suppoiteu.
Ve`ve seen some woikaiounus loi this limitation, Lut they typically enu up using one
ol the conciete implementations ol the inteilace somewheie in the woikaiounu anu
uoing so means that you won`t Le aLle to use any othei implementations ol the
inteilace.
Cuiiently, pioviuing suppoit loi mappeu inteilace piopeities in Entity
Fiamewoik is not high on the team`s piioiity list Lecause theie have
Leen so lew ieguests loi this. Il you want to get moie attention to making
Coue Fiist iecognize inteilace piopeities, vote on (oi suLmit) a sugges-
tion at uata.useivoice.com. Be suie you`ie in the leeuLack aiea loi Entity
Fiamewoik.
Considering Automated Testing with DbContext
In this Look, we`ve useu a console application to uemonstiate many ol the leatuies that
you`ve leaineu aLout. This is to ensuie that ieaueis using the Expiess anu Stanuaiu
veisions ol Visual Stuuio aie aLle to lollow along. Oui peisonal pieleience when Luilu-
ing applications, howevei, is to incluue automateu tests, whethei we use the testing
210 | Chapter 8:Using DbContext in Advanced Scenarios
tools Luilt into Visual Stuuio Piolessional anu Visual Stuuio Ultimate, oi thiiu-paity
tools such as XUnit, NUnit, oi the testing leatuies in ]etBiain`s Reshaipei.
To Le aLle to Luilu llexiLle tests, you`ll want to leveiage the IDbSet inteilace. The
DbSet class you`ve woikeu with thioughout this Look implements IDbSet. Anu the
IDbSet inteilace is wheie the Add, Attach, Remove, anu Create methous come liom.
IDbSet also implements IQueryable<T>, which enaLles LINQ anu Liings along the ex-
tension methous: Find, Include, anu AsNoTracking.
Fiist, let`s look at examples ol automateu tests that you can Luilu anu iun with the
existing BreakAwayContext class anu without having to woik with the IDbSet.
Testing with DbSet
You can Luilu unit tests to valiuate that youi classes woik as expecteu without engaging
Entity Fiamewoik oi the uataLase.
Foi example, the simple test listeu in Example S-5 checks that the FullName piopeity
in the Person type lunctions as expecteu.
Exanp|c 8-5. Ensuring that Iu||Nanc wor|s as cxpcctcd
[TestMethod()]
public void PersonFullNameReturnsFirstNamePlusLastName()
{
var person = new Person
{
FirstName = "Roland",
LastName = "Civet"
};
Assert.AreEqual(person.FullName, "Roland Civet");
}
You can also wiite integiateu tests that check to make suie some ol youi Entity Fiame-
woikielateu logic woiks as expecteu. Example S-6 uisplays a test methou that ensuies
you`ve conliguieu youi class coiiectly to cause Entity Fiamewoik valiuation to notice
that a ielateu Photo piopeity is missing liom a new Person.
Exanp|c 8-. An intcgration tcst to cnsurc that your codc and Entity Irancwor| wor| togcthcr
[TestMethod]
public void ValidationDetectsMissingPhotoInPerson()
{
var person = new Person
{
FirstName = "Mikael",
LastName = "Eliasson"
};
DbEntityValidationResult result;
using (var context = new BreakAwayContext())
{
result = context.Entry(person).GetValidationResult();
Considering Automated Testing with DbContext | 211
}
Assert.IsFalse(result.IsValid);
Assert.IsTrue(result.ValidationErrors
.Any(v => v.ErrorMessage.ToLower()
.Contains("photo field is required")));
}
You can also wiite integiation tests against custom logic that executes uataLase gueiies
oi saves uata Lack to the uataLase to make suie the peisistence is woiking as expecteu.
But theie`s one aiea ol testing that`s a Lit tiickiei with Entity Fiamewoik, which is
testing logic that uses the context to gueiy anu save uata Lut uoes not necessaiily neeu
to make the tiip to the uataLase.
Exploring a Scenario That Unnecessarily Queries the Database
A simplistic example is a methou that peiloims a uataLase gueiy Laseu on some othei
logic. Peihaps you have a iepositoiy methou to ietiieve customeis who have ieseiva-
tions loi a tiip, Lut on|y il that tiip is in the lutuie. Example S-7 shows a single methou
liom a iepositoiy class, GetTravelersOnFutureTrip. The class ueclaiation anu constiuc-
toi aie incluueu in the listing loi claiity.
Exanp|c 8-7. Thc GctTravc|crsOnIuturcTrip ncthod in thc TripRcpository c|ass
public class TripRepository
{
BreakAwayContext _context;
public TripRepository(BreakAwayContext context)
{
_context = context;
}
public List<Person> GetTravelersOnFutureTrip(Trip trip)
{
if (trip.StartDate <= DateTime.Today)
{
return null;
}
return _context.Reservations
.Where(r => r.Trip.Identifier == trip.Identifier)
.Select(r => r.Traveler)
.ToList();
}
}
Il a past tiip is passeu into the methou, the methou will ietuin null. Il a lutuie tiip is
passeu in, the methou will gueiy loi the tiaveleis on the tiip anu ietuin a list ol those
tiaveleis. Recall that the Reservation.Traveler piopeity ietuins a Person type. Il no
ieseivations have Leen maue loi the tiip, the list will contain zeio items.
212 | Chapter 8:Using DbContext in Advanced Scenarios
It woulu Le leasiLle to test that the GetTravelersOnFutureTrip ietuins a null il the past
tiip is passeu in anu that it uoesn`t ietuin a null (iegaiuless ol the size ol the list ietuineu)
il the tiip is in the lutuie.
Example S-S uisplays two tests to check Loth Lits ol logic.
Exanp|c 8-8. Tcsting |ogic oj GctTravc|crsOnIuturcTrip
[TestMethod]
public void GetCustomersOnPastTripReturnsNull()
{
var trip = new Trip { StartDate = DateTime.Today.AddDays(-1) };
using (var context = new BreakAwayContext())
{
var rep = new TripRepository(context);
Assert.IsNull(rep.GetTravelersOnFutureTrip(trip));
}
}
[TestMethod]
public void GetCustomersOnFutureTripDoesNotReturnNull()
{
Database.SetInitializer(new
DropCreateDatabaseIfModelChanges<BreakAwayContext>());
var trip = new Trip { StartDate = DateTime.Today.AddDays(1) };
using (var context = new BreakAwayContext())
{
var rep = new TripRepository(context);
Assert.IsNotNull(rep.GetTravelersOnFutureTrip(trip));
}
}
The liist methou, GetTravelersOnPastTripReturnsNull, will not hit the uataLase. It cie-
ates a minimally populateu tiip instance with a past StartDate. The GetCustomersOnFu
tureTrip methou sees that the StartDate is in the past anu ietuins null, nevei ieaching
the gueiy. Because the uataLase will nevei Le hit Ly the iepositoiy methou, theie`s no
neeu to even woiiy aLout uataLase initialization in this test.
In the seconu test, we expect to gueiy the uataLase, so we`ie setting the initializei to
DropCreateDatabaseIfModelChanges to ensuie that the uataLase exists. Since the test
uoesn`t caie aLout the actual uata, theie`s no neeu to seeu the uataLase. Ve again cieate
a minimal Trip, this time with a lutuie StartDate. The iepositoiy methou will execute
the uataLase gueiy, ieguesting all Reservations wheie the TripIdentifier is
00000000-0000-0000-0000-000000000000 Lecause we uiun`t set that value in the Trip
instance. Theie will Le no iesults anu the methou will ietuin an empty List<Person>.
The test passes Lecause an empty list is not egual to null. You can see that loi this test,
what`s in the uataLase is ol no conseguence, so the tiip to the uataLase is a wasteu
elloit. You only want to make suie that the methou iesponus coiiectly to a lutuie oi
past uate. Howevei, with the BreakAwayContext anu its DbSets, theie`s no avoiuing the
gueiy il a lutuie tiip is passeu in as a paiametei.
Considering Automated Testing with DbContext | 213
Il we Luilu some moie logic into the solution using the IDbSet inteilace, we`ll Le aLle
to take the uataLase out ol the pictuie loi the tests.
Reducing Database Hits in Testing with IDbSet
Il you want to test a methou such as GetTravelersOnFutureTrip without hitting the
uataLase, you`ll neeu to use aLstiactions ol the context anu sets that uo not involve
uataLase inteiaction. Vhat we`ll show you is the key paits ol a moie aLstiacteu solution
so that we can locus on the IDbSet inteilace.
As an alteinative to the BreakAwayContext class, we`ll cieate anothei context that can
cieate entities on the lly without uepenuing on the uataLase. But in oiuei loi us to use
this alteinative context with the iepositoiy, it will neeu to let us execute the coue in the
GetTravelersOnFutureTrip methou. That means it will neeu, loi example, the aLility to
cieate anu execute gueiies. IDbSet gives us the capaLilities that we neeu.
Not only uoes the DbSet class we`ve Leen using implement IDbSet, it also inheiits liom
DbQuery. Anu it`s the DbQuery class that auus in the ieliance on the uataLase. In oui
alteinative context, we`ll use piopeities that implement IDbSet Lut aie not ueiiveu liom
DbQuery. That means we`ll neeu a conciete implementation ol IDbSet, giving us the
aLility to peiloim set logic anu gueiies without inteiacting with a uataLase, ellectively
laking the uataLase inteiaction. So let`s stait Ly cieating this conciete class.
Creating an IDbSet Implementation
Because IDbSet implements IQueryable anu IEnumerable, this new class will neeu to
implement memLeis ol all thiee inteilaces. Example S-9 shows the entiie listing ol the
FakeDbSet class incluuing namespaces useu Ly the class.
The Visual Stuuio IDE, as well as thiiu-paity piouuctivity tools, can help
implement the inteilace memLeis to ieuuce the typing.
Exanp|c 8-9. Thc Ia|cDbSct inp|cncntation oj |DbSct
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace Testing
{
public abstract class FakeDbSet<T> : IDbSet<T>
214 | Chapter 8:Using DbContext in Advanced Scenarios
where T : class, new()
{
readonly ObservableCollection<T> _items;
readonly IQueryable _query;
public FakeDbSet()
{
_items = new ObservableCollection<T>();
_query = _items.AsQueryable();
}
public T Add(T entity)
{
_items.Add(entity);
return entity;
}
public T Attach(T entity)
{
_items.Add(entity);
return entity;
}
public TDerivedEntity Create<TDerivedEntity>()
where TDerivedEntity : class, T
{
return Activator.CreateInstance<TDerivedEntity>();
}
public T Create()
{
return new T();
}
public abstract T Find(params object[] keyValues);
public ObservableCollection<T> Local
{
get
{
return _items;
}
}
public T Remove(T entity)
{
_items.Remove(entity);
return entity;
}
public IEnumerator<T> GetEnumerator()
{
return _items.GetEnumerator();
}
Reducing Database Hits in Testing with IDbSet | 215
IEnumerator IEnumerable.GetEnumerator()
{
return _items.GetEnumerator();
}
public Type ElementType
{
get { return _query.ElementType; }
}
public Expression Expression
{
get { return _query.Expression; }
}
public IQueryProvider Provider
{
get { return _query.Provider; }
}
}
}
Vhen calling DbSet.Attach, Entity Fiamewoik will thiow an exception
il the oLject you aie attaching is alieauy Leing tiackeu Ly the context.
Il this is impoitant Lehavioi loi youi FakeDbSet, you coulu implement
some logic to emulate that Lehavioi.
Theie aie a numLei ol notaLle points to make aLout this implementation.
The liist is that FakeDbSet is an abstract class. That is uue to anothei notaLle point,
which is that theie is an aLstiact methou: Find. As we uon`t anticipate having a DbCon
text to inteiact with, it will Le too uillicult to aiiive at a geneiic way ol hanuling
IDbSet.Find loi any entity. Foi example, the Trip type has a key nameu Identifier. So
Find will neeu to Luilu a gueiy using Identifier, wheieas loi othei types it might neeu
to Luilu a gueiy aiounu Id.
Find is a memLei ol the IDbSet inteilace. The Include, AsNoTracking, anu
Load methous, which you useu eailiei in this Look, aie extension meth-
ous on IQueryable. Vhen using FakeDbSet oi othei IDbSet implemen-
tations, those methous will Le iun without thiowing exceptions. But
they won`t have any impact on youi lake set oi context. Foi example, a
methou that uses Include won`t emulate Include logic in youi lake
gueiies unless you implement special Incluue logic in youi lake DbSets.
FakeDbSet contains two local lielus: an ObservableCollection nameu _items anu an
IQueryable calleu _query. The _items is useu to manage the uata so that we can iesponu
to the IDbSet methous such as Add anu Remove as well as enumeiation supplieu Ly
IEnumerable. Ve`ie using ObservableCollection to make it easy to implement the
216 | Chapter 8:Using DbContext in Advanced Scenarios
Local piopeity. The query lielu is to suppoit memLeis ol IQueryable: Expression anu
Provider.
Also notaLle aie the implementations ol Create. The Create methous aie neeueu to
cieate new entity instances when youi entity types aie using uynamic pioxies. This
allows the context to Le awaie ol the instance. You leaineu aLout woiking with uynamic
pioxies in Chaptei 3.
Vhile acting as a technical ieviewei loi this Look, Mikael Eliasson took
this implementation a step luithei to enaLle the geneiic FakeDbSet to Le
awaie ol the key piopeity so that you`ie not ieguiieu to oveiiiue the
class simply to expanu upon the Find methou. See his solution at https:
//gist.github.con/783ddj75j0bc5a29a9d.
Abstracting BreakAwayContext for Tests
Theie`s one moie lunction to plan loi, which is that the iepositoiy class cuiiently ex-
pects a BreakAwayContext to Le useu to peiloim the gueiy. The cuiient BreakAwayCon
text Liings with it the conciete DbSets anu theieloie the uataLase. Ve`ll aLstiact the
BreakAwayContext class Ly cieating an inteilace that matches the contiact (oi expecta-
tion) ol what shoulu Le in a BreakAwayContext class. Then we can tell the iepositoiy to
expect anything that implements the inteilace, not just the conciete one it`s using now:
public interface IBreakAwayContext
{
IDbSet<Destination> Destinations { get; }
IDbSet<Lodging> Lodgings { get;}
IDbSet<Trip> Trips { get; }
IDbSet<Person> People { get; }
IDbSet<Reservation> Reservations { get; }
IDbSet<Payment> Payments { get; }
IDbSet<Activity> Activities { get; }
int SaveChanges();
}
This inteilace is only uemonstiating what`s neeueu to satisly the iepos-
itoiy methou anu to make suie existing BreakAwayContext examples
liom this chaptei continue to lunction. As you Luilu out an application
anu iepositoiies, you`ll neeu moie leatuies, whethei they aie Luilt into
this paiticulai inteilace oi othei inteilaces anu classes.
Notice that the inteilace ietuins IDbSet piopeities insteau ol conciete classes. This is
how it will Le possiLle to cieate a context that explicitly Luilus anu ietuins FakeDbSets.
Reducing Database Hits in Testing with IDbSet | 217
Now you can mouily the iepositoiy class so that it expects an IBreakAwayContext insteau
ol the BreakAwayContext class. Example S-10 shows the ieviseu liist eight lines ol the
TripRepository class. The _context will now Le an IBreakAwayContext.
Exanp|c 8-10. Bcginning oj rcviscd TripRcpository c|ass
public class TripRepository
{
IBreakAwayContext _context;
public TripRepository(IBreakAwayContext context)
{
_context = context;
}
Il you want to use BreakAwayContext heie, it will now neeu to implement the inteilace.
Fiist, you neeu to auu the inteilace implementation into the class ueclaiation. Anu
Lecause BreakAwayContext is now implementing the inteilace, it neeus to match the
inteilace. Destinations now must ietuin an IDbSet<Destination> anu so loith. You can
see the ielevant poition ol the ieviseu BreakAwayContext class in Example S-11.
Exanp|c 8-11. Brca|AwayContcxt rcviscd to inp|cncnt |Brca|AwayContcxt
public class BreakAwayContext : DbContext, IBreakAwayContext
{
public IDbSet<Destination> Destinations { get; set; }
public IDbSet<Lodging> Lodgings { get; set; }
public IDbSet<Trip> Trips { get; set; }
public IDbSet<Person> People { get; set; }
public IDbSet<Reservation> Reservations { get; set; }
public IDbSet<Payment> Payments { get; set; }
public IDbSet<Activity> Activities { get; set; }
Il you weie to ieiun the GetCustomersOnFutureTripReturnsListOfPeople anu GetCusto
mersOnPastTripReturnsNull tests, they will still pass. The iepositoiy is happy to have
the BreakAwayContext instantiateu in the tests Lecause that class implements IBreakA
wayContext. Anu the DbContext that BreakAwayContext ueiives liom will ensuie that the
IDbSet piopeities ietuin DbSet types so that you continue to inteiact with the uataLase.
Now it`s time to locus on cieating a context that you can use loi testing that won`t hit
the uataLase. Vhen you cieate a new context class that implements IBreakAwayCon
text, you`ll get all ol the IDbSet piopeities. Since oui iepositoiy methou uoesn`t neeu
access to all ol the IDbSet`s, the coue in Example S-12 only initializes the one that we`ll
Le woiking withReservations.
Exanp|c 8-12. Ia|cBrca|AwayContcxt c|ass
using System.Data.Entity;
using DataAccess;
using Model;
namespace Testing
218 | Chapter 8:Using DbContext in Advanced Scenarios
{
public class FakeBreakAwayContext : IBreakAwayContext
{
public FakeBreakAwayContext()
{
Reservations = new ReservationDbSet();
}
public IDbSet<Destination> Destinations { get; private set; }
public IDbSet<Lodging> Lodgings { get; private set; }
public IDbSet<Trip> Trips { get; private set; }
public IDbSet<Person> People { get; private set; }
public IDbSet<Reservation> Reservations { get; private set; }
public IDbSet<Payment> Payments { get; private set; }
public IDbSet<Activity> Activities { get; private set; }
public int SaveChanges()
{
return 0;
}
}
}
Vhat you uon`t see yet in the listing is how the lake sets aie populateu; we`ll leave it
up to the automateu tests to pioviue ielevant uata wheie necessaiy. The tests we`ie
cuiiently locuseu on won`t even ieguiie any seeu uata.
Anu now loi the ueiiveu FakeDbSet classes, which aie key to the FakeBreakAwayCon
text class. Each has its own way to implement Find Laseu on knowleuge ol its key lielu.
Example S-13 shows the ReservationDbSet class neeueu loi this example. You can use
this as a Lasis loi the otheis. ]ust Le suie to use the key piopeities in the Find methou
(loi example, DestinationId in DestinationDbSet). You aie not ieguiieu to implement
the othei lake sets to lollow along with the iest ol this chaptei.
Exanp|c 8-13. RcscrvationDbSct dcriving jron and ovcrriding Ia|cDbSct
using System;
using System.Linq;
using Model;
namespace Testing
{
public class ReservationDbSet : FakeDbSet<Reservation>
{
public override Reservation Find(params object[] keyValues)
{
var keyValue = (int)keyValues.FirstOrDefault();
return this.SingleOrDefault(r => r.ReservationId == keyValue);
}
}
}
This is one appioach to aLstiacting the DbSets. Anothei iuea is piesenteu in the siueLai.
Reducing Database Hits in Testing with IDbSet | 219
FakeDbSets for Similar Types
It is moie likely that you have a common pattein to key names anu may not neeu explicit
Find methous loi each type. In that case you might have a single ueiiveu class that
knows how to gueiy on a single key name. Heie`s an example ol a ueiiveu class uesigneu
loi types whose key piopeities lollow the pattein type name - Iu, such as Destina
tionId, ReservationId, anu otheis:
internal class TypesWithIdDbSet<T> : FakeDbSet<T>
where T : class
{
public override T Find(params object[] keyValues)
{
var keyValue = (int)keyValues.FirstOrDefault();
return this.SingleOrDefault(t => (int)(t.GetType()
.GetProperty(t.GetType().Name + "Id")
.GetValue(t,null)) == keyValue);
}
To see this in action, you`ll neeu to mouily the tests so that they use the
FakeBreakAwayContext insteau ol the BreakAwayContext. The upuateu listing is shown
in Example S-1+.
Exanp|c 8-11. Autonatcd tcst that uscs thc ja|c contcxt, ja|c scts, and ja|c data
[TestMethod]
public void FakeGetCustomersOnPastTripReturnsNull()
{
var trip = new Trip { StartDate = DateTime.Today.AddDays(-1) };
var context = new FakeBreakAwayContext();
var rep = new TripRepository(context);
Assert.IsNull(rep.GetTravelersOnFutureTrip(trip));
}
[TestMethod]
public void FakeGetCustomersOnFutureTripDoesNotReturnNull()
{
var trip = new Trip { StartDate = DateTime.Today.AddDays(1) };
var context = new FakeBreakAwayContext();
var rep = new TripRepository(context);
Assert.IsNotNull(rep.GetTravelersOnFutureTrip(trip));
}
Now you can ieiun the tests. You can ueLug them to veiily that the tests aie inueeu
using the FakeBreakAwayContext.
Now it`s possiLle to veiily that the GetCustomersOnFutureTrip methou in the TripRepo
sitory lunctions piopeily without involving the uataLase.
220 | Chapter 8:Using DbContext in Advanced Scenarios
Reviewing the Implementation
Ve coveieu a lot ol giounu in this section, so let`s catch oui Lieath anu make suie you
haven`t lost sight ol the Lig pictuie. Vhen wiiting automateu tests, it`s not uncommon
to test logic in a methou that, in auuition to the logic youi test is conceineu with, also
happens to inteiact with the uataLase. In oiuei to avoiu this, we cieateu a way to lake
the uataLase inteiaction leveiaging the IDbSet inteilace that`s pioviueu in the DLCon-
text API. Ve cieateu a conciete implementation ol IDbSet calleu FakeDbSet, which is
geneiic so that we can cieate FakeDbSets ol any ol oui mouel types. In oiuei to use non-
uataLase-oiienteu implementations ol IDbSet, we also aLstiacteu the BreakAwayCon
text into its own inteilace that ietuins IDbSet loi gueiying then implementeu FakeDb
Context liom theie.
Figuie S-1 shows how the FakeBreakAwayContext context anu the BreakAwayContext Loth
implement the IBreakAwayContext inteilace. BreakAwayContext uses DbSet piopeities loi
uiiect inteiaction with the uataLase, while FakeBreakAwayContext woiks with FakeDb
Sets populateu with oLjects cieateu in memoiy.
Iigurc 8-1. C|asscs inp|cncnting thc |Brca|AwayContcxt intcrjacc
Vith these aLstiactions in place, TripRepository will woik with any implementation
ol IBreakAwayContext. Foi the sake ol the AutomatedTripTests, the test methous use the
FakeBreakAwayContext anu avoiu uataLase inteiaction while the application uses in-
stances ol BreakAwayContext to pass into the iepositoiy.
Supplying Data to a FakeDbSet
These test methous uiu not neeu any uata availaLle to uo theii joLs, Lut you may have
tests that uo ieguiie some lake uata. Example S-15 shows a new methou that ietuins
the count ol Reservations loi a single Trip.
Reducing Database Hits in Testing with IDbSet | 221
Exanp|c 8-15. Rcpository ncthod to rctricvc a rcscrvation count
public int ReservationCountForTrip(Trip trip)
{
return _context.Reservations
.Where(r => r.Trip.Identifier == trip.Identifier)
.Count();
}
In oiuei to test this methou, the lake context will neeu to contain uata. You can supply
that uata as pait ol a test. Example S-16 uemonstiates one such test, ReservationCount
ForTripReturnsCorrectNumber.
Exanp|c 8-1. Tcsting thc RcscrvationCountIorTrip ncthod
[TestMethod]
public void ReservationCountForTripReturnsCorrectNumber()
{
var context = new FakeBreakAwayContext();
var tripOne = new Trip { Identifier = Guid.NewGuid() };
var tripTwo = new Trip { Identifier = Guid.NewGuid() };
context.Reservations.Add(new Reservation { Trip = tripOne });
context.Reservations.Add(new Reservation { Trip = tripOne });
context.Reservations.Add(new Reservation { Trip = tripTwo });
var rep = new TripRepository(context);
Assert.AreEqual(2, rep.ReservationCountForTrip(tripOne));
}
The context will neeu to contain some Reservation uata in oiuei to ietuin a count.
Vhile you might consiuei auuing a numLei ol Reservation instances that aie lully
uesciiLeu with Payments, a Traveler, anu a Trip, you can see in the example that only
the most minimal inloimation is ieguiieu to peiloim the test accuiately. Thiee new
Reservations aie auueu to the context Lut each Reservation contains no moie inloi-
mation than its Trip piopeity. Notice also that we cieateu two Reservations with the
Trip we aie seaiching loi anu one Reservation assigneu to anothei Trip. Once the
context is seeueu, we can use the asseition to veiily that the gueiy is piopeily lilteiing,
not simply ietuining all ol the Reservations that we cieateu.
Depenuing on the logic you aie testing, you can Luilu up moie complex lake uata in
youi test.
Accessing the Database Directly from DbContext
DbContext communicates with the uataLase any time you execute a gueiy oi call Save
Changes. You can take auvantage ol DbContext`s access to the uataLase thiough its
Database piopeity. Vith DbContext.Database, you can communicate uiiectly with the
uataLase il youi application calls loi such inteiaction.
222 | Chapter 8:Using DbContext in Advanced Scenarios
You may not have iealizeu that Coue Fiist leveiages the Database piopeity loi the ua-
taLase initialization tasks. It uses the Database.Exists piopeity to check loi the uata-
Lase. Database has a Delete methou anu a Create methou anu even one calleu Crea
teIfNotExists. All loui ol these memLeis aie puLlic, so you coulu use them uiiectly il
you want to. In lact, eaily technical pieviews ol Coue Fiist ieguiieu uevelopeis to use
those piopeities anu methous to peiloim uataLase initialization manually. It wasn`t
until latei that the SetInitializer classes weie intiouuceu which encapsulateu the most
common initialization woikllows.
Once youi DbContext is instantiateu, it will Le awaie ol the connection stiing it will use
to woik with the uataLase, even il you haven`t yet peiloimeu any tasks that woulu
initiate the connection. You can use the DbContext.Database piopeity to inteiact with
the connection oi the uataLase itsell.
Not all ol the Database memLeis ieguiie uiiect inteiaction with the uataLase. Foi ex-
ample, heie is some coue that wiites the connection stiing ol the Database associateu
with that context to a console winuow using the Database.Connection piopeity, which
ietuins a System.Data.Common.DbDataConnection:
using (var context=new BreakAwayContext())
{
Console.WriteLine(context.Database.Connection.ConnectionString);
}
A moie common use ol the Database piopeity is to execute iaw gueiies anu commanus
uiiectly on the uataLase loi those ouu occasions when you neeu to peiloim a gueiy
that`s not suppoiteu Ly youi mouel oi Ly Entity Fiamewoik.
Executing Queries with Database.SqlQuery and DbSet.SqlQuery
DbContext.Database.SqlQuery lets you execute a gueiy on the uataLase anu ietuin any
type that you specily. It will use the connection liom the context that you aie calling
Database.SqlQuery liom. Foi example, the uataLase might have a view nameu Destina
tionSummaryView. Even il theie is no DestinationSummary mouel type, you can ueclaie
the class anu then gueiy the view using SqlQuery. As long as the iesults ol the
SqlQuery match the type that you want it to populate, Entity Fiamewoik will Le happy.
The DestinationSummary class might look something like Example S-17.
Exanp|c 8-17. DcstinationSunnary c|ass
public class DestinationSummary
{
public int DestinationId { get; set; }
public string Name { get; set; }
public int LodgingCount { get; set; }
public int ResortCount { get; set; }
}
Then you can call the Database.SqlQuery liom an instance ol DbContext as lollows:
Accessing the Database Directly from DbContext | 223
var summary = context.Database.SqlQuery<DestinationSummary>(
"SELECT * FROM dbo.DestinationSummaryView");
SqlQuery ietuins an SqlDbQuery. You`ll have to execute the SqlQuery with a LINQ
methou such as ToList to execute the gueiy.
What About the Database Class in a Fake Context?
Il you make calls to DbContext.Database in youi application anu want to Luilu tests
aiounu that logic, you`ll want youi lake contexts to Le aLle to hanule that logic. Foi
example, you may have iaw SqlQuery statements in methous loi which you want to
wiite automateu tests using the lake context anu lake sets you leaineu aLout eailiei.
One appioach to coveiing this leatuie in youi tests woulu Le to encapsulate the iaw
SqlQuery coue into methous that aie pait ol IBreakAwayContext. Then you can imple-
ment those methous as uesiieu in BreakAwayContext anu any othei classes that also
implement the inteilace.
Foi example, you coulu auu this methou to IBreakAwayContext:
List<DestinationSummary> GetDestinationSummary();
In BieakAwayContext you coulu implement it as lollows:
public List<DestinationSummary> GetDestinationSummary()
{
return this.Database.SqlQuery<DestinationSummary>(
"SELECT * FROM dbo.DestinationSummaryView").ToList();
}
Anu in the FakeBreakAwayContext, you coulu initially implement the methou like this:
public List<DestinationSummary> GetDestinationSummary()
{
throw new NotImplementedException();
}
Vhen the time comes that you want uata to Le ietuineu Ly FakeBreakAwayContext.Get
DestinationSummary, you coulu auu in logic to cieate anu ietuin one oi moie Destina
tionSummary instances in the list.
Now iathei than calling DbContext.Database.SqlQuery uiiectly in youi coue to get the
DestinationSummary, you can call the GetDestinationSummary methou that is suppoiteu
Ly the inteilace.
Il youi gueiy is ietuining types that aie exposeu in the context thiough DbSet piopeities,
you can use the DbSet.SqlQuery methou insteau. This veision ol SqlQuery uoes not
ieguiie you to supply the ietuin type as a paiametei, since the DbSet knows the type.
Example S-1S shows DbSet.SqlQuery, which uemonstiates how explicit you neeu to Le
when constiucting the gueiy. The Destination maps to the baga.Locations taLle anu
that taLle`s lielu names uon`t match the Destination piopeity names. SqlQuery expects
the iesults to match the type exactly, incluuing matching the names anu types coiiectly.
The oiuei ol the columns in the iesult set is not ciitical.
224 | Chapter 8:Using DbContext in Advanced Scenarios
Exanp|c 8-18. Exccuting a qucry with Sq|Qucry
var dests = context.Database.SqlQuery<Destination>
(@"SELECT LocationId as DestinationId, LocationName as Name,
Description, Country, Photo
FROM baga.locations where country={0}","Australia");
var results = dests.ToList();
The iesulting dests will Le a List ol Destination types. Because ol the stiing Foimat
syntax (that is, {0}), Entity Fiamewoik will execute this in the uataLase as a paiame-
teiizeu gueiy.
SqlQuery is not suppoiteu loi types that contain complex type piopei-
ties. Theieloie you cannot ietuin a Person type liom a SqlQuery Lecause
it has a PersonalInfo lielu anu an Address lielu that aie Loth complex
types.
Il you neeu to Luilu uynamic gueiies, we iecommenu that you Le conscientious ol the
possiLility ol SQL injection oi othei secuiity attacks. You shoulu Luilu paiameteiizeu
gueiies as you uiu in Example S-1S, iathei than concatenate values uiiectly into the
SQL. Alteinatively, you can use an oveiloau ol SqlQuery that accepts SqlParameters.
Example S-19 shows how you woulu use paiameteis with SqlQuery. The paiticulai
gueiy coulu Le peiloimeu veiy easily with LINQ to Entities anu is only useu heie loi
uemonstiation puiposes.
Exanp|c 8-19. Exccuting a qucry with Sq|Qucry
var destSql = @"SELECT LocationId as DestinationId,
LocationName as Name, Description,
Country,Photo
FROM baga.locations
WHERE country=@country";
var dests = context.Database.SqlQuery<Destination>
(destSql, new SqlParameter("@country", "Australia"))
.ToList();
Eailiei in this chaptei, we mouilieu BreakAwayContext to use IDbSet piopeities insteau
ol conciete DbSet piopeities. SqlQuery is a methou ol DbSet. Il you aie using IDbSet,
you`ll liist neeu to cast it to DbSet in oiuei to use DbSet.SqlQuery. That means that loi
methous that incluue SqlQuery, you`ll Le limiteu with iespect to what types ol auto-
mateu tests you can peiloim.
Heie is the SqlQuery statement liom Example S-19 ieviseu to woik with an IDbSet that
neeus to Le cast to DbSet:
var dests = ((DbSet<Destination>)context.Destinations)
.SqlQuery(destSql, new SqlParameter("@country", "Brazil"))
.ToList();
Accessing the Database Directly from DbContext | 225
Tracking Results of SqlQuery
Vhen you execute a SqlQuery liom Database, the iesults aie nevei tiackeu Ly the con-
text, even il the gueiy ietuins types that aie in the mouel anu known Ly the context. Il
you uo not want the iesults to Le change-tiackeu, use DbContext.Database.SqlQuery.
Results ol a DbSet.SqlQuery will Le tiackeu Ly the context. Ensuiing that iesults aie
change-tiackeu is the piimaiy ieason you woulu choose to use DbSet.SqlQuery ovei
Database.SqlQuery.
Executing Commands from the Database Class
DbContext.Database also lets you execute commanus, not just gueiies, on the uataLase
uiiectly il you encountei an ouu lunction you want to peiloim in the uataLase. You
can uo this with the ExecuteSqlCommand methou. ExecuteSqlCommand takes two paiam-
eteis: a SQL stiing expiession anu an aiiay ol SqlParameters. Although as you saw with
the SqlQuery, you might pielei using the cleanei-looking stiing Foimat syntax to ach-
ieve paiameteiizeu commanus. ExecuteSqlCommand ietuins an int iepiesenting the
numLei ol iows allecteu in the uataLase.
Like SqlQuery, ExecuteSqlCommand will use the connection ol the context liom which
you aie calling the commanu. Depenuing on the peimissions gianteu loi the active
context instance, you coulu execute Inseit, Upuate, anu Delete commanus oi even
commanus that allect the uataLase schema.
ExecuteSqlCommand is uesigneu to hanule special scenaiios anu isn`t meant as a ieplace-
ment loi Entity Fiamewoik`s main mechanisms loi peisisting uata in the uataLase.
Il you have ieau Progranning Entity Irancwor|: Codc Iirst, you may iecall how Exe
cuteSqlCommand was useu to enhance seeuing a uataLase uuiing initialization. See the
siueLai.
Using ExecuteSqlCommand to Enhance Database Seeding
The lollowing is extiacteu liom Chaptei 6 ol Progranning Entity Irancwor|: Codc
Iirst.
In auuition to seeuing a uataLase when Coue Fiist cieates it, you may want to allect
the uataLase in ways that can`t Le uone with conliguiations oi uata seeuing. Foi
example, you may want to cieate an Index on the Name lielu ol the Lodgings taLle to
speeu up seaiches Ly name.
You can achieve this Ly calling the DbContext.Database.ExecuteSqlCommand methou
along with the SQL to cieate the inuex insiue the Seed methou. Heie is an example ol
a Seeu methou that loices this Inuex to Le cieateu Leloie the uata is inseiteu:
protected override void Seed(BreakAwayContext context)
{
context.Database.ExecuteSqlCommand
("CREATE INDEX IX_Lodgings_Name ON Lodgings (Name)");
226 | Chapter 8:Using DbContext in Advanced Scenarios
Coue Fiist Migiations, availaLle in Entity Fiamewoik +.3, incluues native suppoit loi
cieating inuexes in youi uataLase without uiopping uown to ExecuteSqlCommand.
Providing Multiple Targeted Contexts in Your Application
So lai in this Look we`ve useu a single context class, BreakAwayContext, to iepiesent oui
mouel anu expose all ol oui uomain classes loi a solution`s uata access neeus. In a laige
solution, it is likely that you have uilleient aieas ol youi application that auuiess a
specilic Lusiness piocess anu will only ieguiie inteiaction with a suLset ol youi uomain
classes. Il you have a lot ol uomain classes, theie aie a numLei ol Lenelits to cieating
DbContexts that aie taigeteu to these vaiious piocesses iathei than one all-puipose
context. Most impoitant is maintainaLility. As youi application giows, so will the
DbContext class. It can Lecome unwieluy il it`s iesponsiLle loi many DbSet piopeities
anu lluent conliguiations loi many classes. Auuing anu mouilying existing logic will
get moie uillicult. Il you have multiple contexts, each iesponsiLle loi a ceitain lunction
ol youi application, they will each contain a smallei set ol piopeities anu conliguia-
tions. It will Le much easiei to maintain each context as well as locate the logic you
neeu within it.
Peiloimance is anothei consiueiation. Vhen Entity Fiamewoik cieates an in-memoiy
mouel ol the context, the laigei the context is the moie iesouices aie expenueu to
geneiate anu maintain that in-memoiy mouel.
Reusing Classes, Configurations, and Validation Across Multiple Contexts
Thioughout this Look you`ve seen us attempt to oiganize anu ielactoi coue as oui
application giew. Fiom the Leginning, we`ve kept the uomain classes in theii own
pioject. Il you have ieau Progranning Entity Irancwor|: Codc Iirst, you saw that when
we useu the Fluent API to conliguie oui entities, we cieateu sepaiate classes to contain
the Fluent conliguiation logic loi each type. In the valiuation chapteis, you saw that
we encapsulateu logic liom the ValidateEntity methou so that the methou woulun`t
get loaueu uown with uetails ol each custom valiuation Leing peiloimeu. Oiganizing
logic in this way makes it easiei to ieuse that logic anu lets us shaie classes anu logic
acioss oui multiple contexts.
These smallei contexts lollow a pattein ciitical to Domain Diiven De-
sign calleu Boundcd Contcxts. I like to think ol the technigue ol aligning
the uesign ol each inuiviuual small DbContext class with its ielateu Do-
main Contcxt as Boundcd DbContcxts.
Using the BieakAway uomain, let`s look at a conciete example.
Providing Multiple Targeted Contexts in Your Application | 227
Let`s say that the Sales uepaitment is iesponsiLle loi selling ieseivations. They woulu
neeu to auu ieseivations anu payments. Sales uoes not uesign tiips oi cieate ielation-
ships with lougings. Sales woulu neeu to peiloim a Lit ol customei seivice as well. Il
the peison puichasing the ieseivation is a new customei, Sales woulu neeu to auu the
customei. Sales woulu neeu to look up tiips anu uestinations Lut only as a lookup list.
They woulun`t neeu to woik with Trip anu Destination entities in a way that woulu
enaLle change-tiacking anu mouilication.
In a simple scenaiio, this means Sales woulu neeu access to
Peison (Lookup, Auu, Euit)
Auuiess (View liom Peison, Auu, Euit)
Reseivations (Lookup, Auu, Euit)
Payments (View liom Reseivation, Auu)
Reau-Only Lists: Tiip Dates, Destination Name, Activities
Vhat aLout the Tiip Planning uepaitment? They neeu access to
Tiip (Lookup, Auu, Euit)
Destination (Lookup, Auu, Euit)
Activity (Lookup, Auu, Euit)
Louging (Lookup, Auu, Euit)
Peison (Lookup, Auu, Euit)
Vhat might the contexts loi these two application scenaiios look like?
SalesContext coulu have DbSet piopeities loi People, Reservations, anu Trips. This
woulu allow sales to look up oi auu People anu theii Addresses; look up anu auu
Reservations anu theii piopeities; anu seaich loi Trips along with theii Destination,
Lodging, anu Activity uetails. SalesContext can uiaw liom the pool ol availaLle classes
in the Model pioject. Example S-20 shows a minimal SalesContext class.
Exanp|c 8-20. Sa|csContcxt c|ass
public class SalesContext:DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Reservation> Reservations { get; set; }
public DbSet<Trip> Trips { get; set; }
}
Let`s see what Coue Fiist uiaws into the mouel Laseu on these thiee DbSets.
Heie`s a small console methou that instantiates the context, anu then uiills uown to
the ObjectContext to gueiy loi EntityTypes liom conceptual mouel (CSDL) using the
MetauataVoikspace API. MetadataWorkspace ieaus the in-memoiy metauata ol the
mouel anu then lists them in the console winuow. You`ll neeu a using loi the Sys
tem.Data.Metadata.Edm namespace to use the EntityType class.
228 | Chapter 8:Using DbContext in Advanced Scenarios
Exanp|c 8-21. Iorcing nodc| crcation and |isting typcs
private static void ForceContextModelCreation()
{
using (var context = new SalesContext())
{
var entityTypes = ((IObjectContextAdapter)context).ObjectContext
.MetadataWorkspace.GetItems<EntityType>(DataSpace.CSpace);

foreach (var entityType in entityTypes)
{
Console.WriteLine(entityType.Name);
}
}
}
Below aie the SalesContext mouel types as they aie listeu in the console winuow:
Person
Lodging
Destination
InternetSpecial
Resort
Hostel
PersonPhoto
Reservation
Trip
Activity
Payment
Not only uo you see the thiee types ietuineu Ly the DbSets (Person, Reservation, anu
Trip), Lut all ielateu types as well. Vhen Luiluing a mouel, Coue Fiist will pull in all
types that aie ieachaLle Ly types in the mouel. Foi example, Trip has a ielationship to
Destination, so Destination class was pulleu into the mouel as well. The complex types,
Address anu PersonInfo, aie in the mouel Lut not iepiesenteu in the scieenshot. You
can see them Ly ieguesting GetItems<ComplexType>(DataSpace.CSpace)liom the Metada
taWorkspace.
Coue Fiist useu its convention to ueciue what types neeueu to Le in the mouel. In this
case, all ol the types that it incluueu make sense loi this mouel. But the context is still
simplei to woik with, since it uoesn`t have explicit DbSets loi all ol those types.
Il you aie using the Fluent API to conliguie the mouel, Le suie that
mappings necessaiy loi all ol the classes in youi mouel aie incluueu in
youi conliguiations, not just mappings loi types iepiesenteu Ly DbSets.
By uelault Coue Fiist uiu us a lavoi. It cieateu a new SalesContext uataLase. That`s not
the uesiieu ellect. Vhat we`ll want is to have the complete BreakAwayContext uataLase
availaLle to us. Let`s set this pioLlem asiue loi latei in this chaptei anu insteau, look at
oui othei taigeteu context, TripPlanning:
Providing Multiple Targeted Contexts in Your Application | 229
public class TripPlanningContext : DbContext
{
public DbSet<Trip> Trips { get; set; }
public DbSet<Destination> Destinations { get; set; }
public DbSet<Lodging> Lodgings { get; set; }
public DbSet<Activity> Activities { get; set; }
public DbSet<Person> People { get; set; }
}
Ve`ie using common types in Loth the SalesContext anu TripPlanning
Context. Vhat aLout shaiing entity instances acioss instances ol these
contexts? Reau the siueLai at the enu ol the chaptei (Shaiing Types
Acioss Multiple DLContexts on page 233) loi some insight into this
scenaiio.
Mouily the ForceContextModelCreation methou liom Example S-21 to instantiate the
context vaiiaLle as a TripPlanningContext iathei than a SalesContext. The output ol
the mouilieu methou, showing the types in the TripPlanningContext mouel is as lollows:
Trip
Destination
Lodging
InternetSpecial
Person
PersonPhoto
Reservation
Payment
Resort
Hostel
Activity
Coue Fiist pulleu moie entities into the mouel than we will neeu. Ve`ll want Person
Photo loi cieating any new Person types. But theie`s no neeu loi Reservation anu Pay
ment types in the mouel.
In iaie cases, it will Le sale to use the Fluent API`s Ignore methou to tiim Lack entities
that you uon`t want in the mouel. But uoing so coulu easily leau you to Lieaking iela-
tionship constiaints anu losing access to loieign key piopeities in a mouel.
It is not iecommenueu to iemove entities liom a mouel that weie pulleu
in Ly Coue Fiist Lecause they aie ieachaLle Ly anothei entity. Theie`s
a goou chance that you will cieate pioLlems with ielationship con-
stiaints anu loieign keys, which may oi may not Le caught Ly excep-
tions. This coulu leau to invaliu uata in youi uataLase.
It won`t always Le possiLle to ignoie a class that you uon`t neeu in the mouel. Foi
example, il you ueciueu to iemove Person liom TripPlanningContext, an exception will
Le thiown when Coue Fiist attempts to cieate the mouel. The ieason is that theie aie
230 | Chapter 8:Using DbContext in Advanced Scenarios
conliguiations in the Lodging class that uepenu on the Person class. The DbModel
Builder will tiy to woik that out Lut will lail Lecause theie`s no Person in the mouel.
Ensuring That All DbContexts Use a Single Database
Now let`s see how to use these small contexts against a single uataLase.
By convention, the context will loice Coue Fiist to look loi a uataLase with the same
name as the context. That`s not what we want. Ve want them all to use the BieakA-
wayContext uataLase. Ve coulu use a connection stiing in the conlig lile to point to
the coiiect uataLase. But theie`s anothei wiinkle. The context will look loi a connection
stiing that matches its name. Ve`u have to have a SalesContext connection, a Trip
PlanningContext connection, anu auuitional connections specilieu in the conlig lile loi
eveiy context. That`s not veiy maintainaLle.
Anothei tool we have at oui uisposal is an oveiloau ol the context constiuctoi. Ve can
pass in a uataLase name. That solves the pioLlem; howevei, it means that eveiy time
we instantiate a new SalesContext oi any othei context in oui solution, we`u have to
pass in a stiing oi a vaiiaLle iepiesenting BieakAwayContext. That`s also unuesiia-
Lle. Not only is it unnecessaiily iepetitive, you have to woiiy aLout someone loigetting
to use the oveiloau. Howevei, you coulu lean on a Lase class to apply the connection
stiing loi you each time.
Example S-22 shows a hanuy geneiic Lase class pattein suggesteu Ly Aithui Vickeis
liom the Entity Fiamewoik team. Not only will this Lase class ensuie that the any
context that inheiits liom it uses the appiopiiate connection, Lut Ly setting the ini-
tializei on the given context to null, it ensuies that Coue Fiist won`t attempt to initialize
a uataLase loi the context. Because the constiuctoi is telling DLContext to look loi a
connection in the conliguiation lile, you woulu neeu to auu a connection stiing nameu
Lieakaway to youi app.conjig oi wcb.conjig lile.
Exanp|c 8-22. BascContcxt to sct conncction string and disab|c databasc initia|ization
public class BaseContext<TContext> : DbContext
where TContext : DbContext
{
static BaseContext()
{
Database.SetInitializer<TContext>(null);
}
protected BaseContext()
: base("name=breakaway")
{
}
}
Providing Multiple Targeted Contexts in Your Application | 231
Note that the BaseContext constiuctoi is static. That ensuies that the
initializei setting is set pei application instance ol the given constiuctoi,
not pei context instance. Initializei settings shoulu Le application-wiue,
so this is uesiiaLle.
You can then mouily the context classes to inheiit liom BaseContext insteau ol inhei-
iting liom DbContext uiiectly.
Heie aie the ieviseu ueclaiations loi TripPlanningContext anu SalesContext:
public class TripPlanningContext : BaseContext<TripPlanningContext>
public class SalesContext : BaseContext<SalesContext>
Validating Relationship Constraints and Other Validations with Multiple
Contexts
Each context will Le iesponsiLle loi its own valiuations. The context will check to make
suie ielationship constiaints Letween classes aie satislieu. Il you have ieguiieu iela-
tionships Letween classes, il one ol those classes is in a mouel, the othei neeus to Le as
well. You shoulu take auvantage ol automateu testing to make suie that youi small
mouels uon`t Lieak ielationships.
Il you have custom logic that neeus to Le tiiggeieu in ValidateEntity, Le suie to put it
in a pioject that is accessiLle Ly youi uilleient contexts. In Chaptei 7, loi example,
Example 7-5 uemonstiateu calling out to a custom methou, ValidateLodging liom
ValidateEntity. In any context class wheie you anticipate using Lodging types, you`ll
pioLaLly want to call that ValidateLodging methou. So iathei than ueclaie Validate
Lodging a private methou insiue ol the BreakAwayContext class lile, you coulu make it
puLlic anu put it into a sepaiate pioject wheie it can Le calleu liom othei context classes.
Getting Code First to Create Full BreakAwayContext Database
You still may want to leveiage Coue Fiist`s uataLase initialization while ueveloping this
application. Even though you uon`t want the SalesContext oi TripPlanningContext
classes to Le involveu with uataLase initialization, you coulu cieate a DbContext class
just loi the sake ol uataLase initialization. It might even involve Coue Fiist Migiations
oi a custom initializei with a Seed methou. All you shoulu neeu to uo is cieate one
context class that will involve all ol the classes. You can ensuie all ol the classes aie
pulleu into the mouel Ly cieating DbSets loi each ol the entities iathei than hoping that
ielationships anu hieiaichies Letween youi uomain classes will cieate all ol the ielevant
taLles in the uataLase. Il you aie using lluent conliguiations, you`ll neeu to auu all ol
them to the modelBuilder.Configurations to Le suie that all ol the mappings aie cieateu
piopeily. In oui test classes we have one test, which then iecieates the uataLase loi us
as neeueu:
232 | Chapter 8:Using DbContext in Advanced Scenarios
private static void ForceBreakAwayDatabaseCreation()
{
Database.SetInitializer(new InitializeBagaDatabaseWithSeedData());
using (var context = new BreakAwayContext())
{
context.Database.Initialize(force: false);
}
}
RememLei that the InitializeBagaDatabaseWithSeedData class cui-
iently ueiives liom DropCreateDatabaseAlways.
Sharing Types Across Multiple DbContexts
Vhen cieating multiple smallei Lounueu DLContexts, you aie aLle to ieuse types anu
conliguiations. Foi example Loth the SalesContext anu the TripPlanningContext en-
capsulate Trip types, Person types, anu moie.
Developeis olten ask aLout shaiing instances Letween multiple contexts. Beloie uoing
so, the liist guestion you shoulu ask youisell is il you have uelineu youi contexts well
loi youi uomain il you linu a neeu to shaie instances ol youi types. Il you aie satislieu
that moving instances Letween contexts is tiuly what you want to uo, theie aie a lew
iules you shoulu Le consiueiate ol.
An entity can only Le attacheu to one context at a time. This aichitectuie woiks
Lest with shoit-liveu contexts wheie the instance to Le shaieu will Le completely
uisassociateu liom one context Leloie it is attacheu to anothei.
Entities that aie attacheu to uilleient contexts cannot Le attacheu to one anothei.
Heie`s some coue that uemonstiates moving an entity liom one context to anothei.
The coue instantiates a SalesContext anu uses that to ietiieve a Person instance. Then
we uetach that peison liom the context Ly setting its Entry.State to Detached. Now the
peison is liee to Le attacheu to a TripPlanningContext. Once attacheu, the coue loaus
all ol the lougings loi which that peison is a piimaiy contact:
Person person;
using (var sC = new SalesContext())
{
person = sC.People.FirstOrDefault(p => p.FirstName == "Dave");
}
using (var tC = new TripPlanningContext())
{
tC.People.Attach(person);
tC.Entry(person).Collection(p => p.PrimaryContactFor).Load();
}
Providing Multiple Targeted Contexts in Your Application | 233
You shoulu Le caielul aLout (oi just avoiu) moving Auueu, Mouilieu, oi Deleteu entities
liom one context to anothei. You can Lenelit liom many ol the lessons you leaineu
aLout woiking with uisconnecteu entities anu giaphs in Chaptei + ol this Look when
moving entities Letween contexts.
234 | Chapter 8:Using DbContext in Advanced Scenarios
CHAPTER 9
Whats Coming Next for
Entity Framework
So lai, this Look has walkeu thiough all the DLContext API lunctionality that was
availaLle at the time ol wiiting. The companion to this Look, Progranning Entity
Irancwor|: Codc Iirst, coveis the iemainuei ol the lunctionality that was availaLle in
the EntityFramework NuGet package at the time ol wiiting. Howevei, theie aie some
notaLle leatuies that will soon Le availaLle loi pieview.
The Entity Fiamewoik team has inuicateu that they aie aLout to ielease Entity Fiame-
woik 5.0 (EF 5.0), which will incluue some long-awaiteu leatuies, incluuing suppoit
loi enum piopeities anu spatial uata.
Understanding Entity Frameworks Version Numbers
Histoiically, Entity Fiamewoik ieleases have Leen stiongly tieu to .NET Fiamewoik
ieleases. The liist veision ol Entity Fiamewoik shippeu with .NET 3.5 SP1. The seconu
ielease was incluueu in .NET + anu was nameu Entity Fiamewoik + (EF +), to align
with the .NET veision. The DLContext API anu Coue Fiist weie ieleaseu out-ol-Lanu
ol the .NET Fiamewoik as EF +.1. This numLei was chosen to inuicate that it was a
small upuate that Luilt on the lunctionality in .NET +.
Vhen the time came to ship Lug lixes loi EF +.1, the team ieleaseu EF +.1 Upuate 1.
This ielease, anu some othei ieleases liom Miciosolt, causeu some conlusion in the
community aLout how ieleases aie veisioneu. Baseu on this leeuLack, the Entity
Fiamewoik team has now auopteu scnantic vcrsioning loi its ieleases. You can ieau
moie aLout semantic veisioning at http://scnvcr.org.
Altei auopting semantic veisioning, the Entity Fiamewoik team ieleaseu EF +.2, which
incluueu some Lug lixes. They then ieleaseu EF +.3, which incluueu the new Coue Fiist
Migiations leatuie, along with a hanulul ol high-piioiity Lug lixes.
235
Entity Framework 5.0
EF 5.0 intiouuces a set ol long-awaiteu leatuies anu makes use ol some woik the Entity
Fiamewoik team has uone in the next veision ol the .NET Fiamewoik (.NET +.5).
Entity Fiamewoik 5.0 will Lecome availaLle as an upuate to the EntityFramework NuGet
package. At the time ol wiiting, a pieielease veision ol EF 5.0 was not yet availaLle.
The Entity Fiamewoik team has inuicateu they will Le making the liist pieview ol EF
5.0 availaLle shoitly altei the next pieview ol .NET +.5 is ieleaseu. The iest ol this
section pioviues a Liiel oveiview ol the majoi leatuies that the Entity Fiamewoik team
has inuicateu will Le in the EF 5.0 ielease.
Entity Fiamewoik 5.0 is uepenuent on .NET +.5 Lecause a numLei ol the new leatuies
ieguiieu changes to the coie Entity Fiamewoik components. These coie components,
incluuing ObjectContext anu othei ielateu types, aie still pait ol the .NET Fiamewoik.
Foi example, auuing enum suppoit to the DLContext API anu Coue Fiist ieguiieu
auuing enum suppoit to ObjectContext. The Entity Fiamewoik team is woiking to
move moie ol these coie components out ol the .NET Fiamewoik in the lutuie. This
woulu enaLle moie leatuies to Le auueu without upuates to the .NET Fiamewoik.
Enums
Developeis have Leen asking loi suppoit loi enum piopeities in Entity Fiamewoik
since it was liist ieleaseu. This leatuie will allow you to ueline a piopeity on a uomain
class that is an enum type anu map it to a uataLase column ol an integei type. Entity
Fiamewoik will then conveit the uataLase value to anu liom the ielevant enum as it
gueiies anu saves uata.
Spatial Data
SQL Seivei 200S intiouuceu suppoit loi geometiy anu geogiaphy uata types. A set ol
opeiatois is also incluueu to allow gueiies to analyze spatial uata. Foi example, a gueiy
can liltei Laseu on the uistance Letween two geogiaphic locations. EF 5.0 will allow
new spatial uata types to Le exposeu as piopeities on youi classes anu map them to
spatial columns in youi uataLase. You will also Le aLle to wiite LINQ gueiies that make
use ol the spatial opeiatois to liltei, soit, anu gioup Laseu on spatial calculations pei-
loimeu in the uataLase.
Performance Improvements
EF 5.0 will incluue some upuates anu a new leatuie to enhance peiloimance. The most
notaLle new leatuie is autonatic caching oj conpi|cd L|NQ qucrics. Vhen Entity
Fiamewoik iuns a LINQ gueiy, it goes thiough a piocess ol conveiting youi LINQ
gueiy into SQL to Le executeu in the uataLase. This piocess is known as qucry conpi-
|ation anu is an expensive opeiation, especially loi complex gueiies. In the past you
236 | Chapter 9:Whats Coming Next for Entity Framework
coulu use compileu gueiies to make Entity Fiamewoik cache the compileu gueiy anu
ieuse it, potentially with uilleient paiameteis. In EF 5.0 the compileu gueiy will Le
automatically cacheu anu ieuseu il you iun the same gueiy again, even il you have
uilleient paiametei values in the gueiy.
Foi example, il you iun a LINQ gueiy loi all Locations wheie the DestinationId is egual
to a value stoieu in an integei vaiiaLle, Entity Fiamewoik will cache the tianslation
that knows how to select a set ol Locations lilteieu Ly DestinationId. Il you latei iun
anothei gueiy loi all Locations wheie the DestinationId is egual to a value stoieu in a
uilleient integei vaiiaLle, Entity Fiamewoik will ieuse the cacheu tianslation, even il
the value stoieu in the vaiiaLle is uilleient than the liist gueiy.
EF 5.0 will also incluue impiovements to the SQL it geneiates. The SQL will Le simplei
to ieau anu will iesult in peiloimance gains. In paiticulai, theie aie impiovements to
make gueiying lastei when woiking with an inheiitance hieiaichy that is mappeu using
the taLle-pei-type (TPT) stiategy.
Multiple Result Sets from Stored Procedures
Entity Fiamewoik allows you to use stoieu pioceuuies to gueiy loi uata anu to inseit,
upuate, anu uelete uata. Pieviously, when selecting uata, the stoieu pioceuuie coulu
only ietuin a single iesult set. EF 5.0 will allow you to use a stoieu pioceuuie that
ietuins multiple iesult sets. The uilleient iesult sets can iepiesent uata loi uilleient
entity types oi piojections.
Vhile Entity Fiamewoik suppoits mapping to stoieu pioceuuies, this
lunctionality is not suppoiteu in Coue Fiist. The Entity Fiamewoik
team is not planning to auu stoieu pioceuuie suppoit to Coue Fiist in
EF 5.0. They have inuicateu that theie aie no uelinite plans aiounu when
this will Le auueu.
Table Value Functions
TaLle Value Functions (TVFs) aie lunctions in the uataLase that ietuin a well-known
iesult set. TVFs aie composaLle anu can theieloie Le incluueu in a gueiy in much the
same way a taLle can. EF 5.0 allows you to incluue TVFs in youi mouel anu ueline a
lunction on youi context that iepiesents the TVF. This lunction can then Le useu in a
LINQ gueiy in the same way you woulu use a DLSet. TVFs will not Le suppoiteu in
Coue Fiist in EF 5.0.
Entity Framework 5.0 | 237
Following the Entity Framework Team
Theie aie a numLei ol ways you can keep up to uate on new leatuies that the Entity
Fiamewoik team is uevelopinganu even inlluence what leatuies they woik on next.
The ADO.NET Team Blog is useu Ly the EF team to shaie announcements aLout new
anu upcoming ieleases. The EF team also has an EF Design Blog, wheie they shaie eaily
thinking aLout leatuies they aie aLout to stait woiking on. This allows you to have
input into the uesign ol leatuies Leloie they aie implementeu anu enu up in a pieview.
Finally, the EF team has a usei voice site wheie you can auu anu vote on leatuie ieguests.
238 | Chapter 9:Whats Coming Next for Entity Framework
About the Authors
JuIia Lerman is the leauing inuepenuent authoiity on the Entity Fiamewoik anu has
Leen using anu teaching the technology since its 2006 inception. She is well known in
the .NET community as a Miciosolt MVP, ASPInsiuei, anu INETA Speakei. ]ulia is a
lieguent piesentei at technical conleiences aiounu the woilu anu wiites aiticles loi
many well-known technical puLlications, incluuing the Data Points column in
MSDN Magazinc. ]ulia lives in Veimont with hei husLanu, Rich, anu gigantic uog,
Sampson, wheie she iuns the Veimont.NET Usei Gioup. You can ieau hei Llog at
www.thcdatajarn.con/b|og anu lollow hei on Twittei at julieleiman.
Rowan MiIIer woiks as a piogiam managei loi the ADO.NET Entity Fiamewoik team
at Miciosolt. He speaks at technical conleiences anu Llogs at http://roni||cr.con.
Rowan lives in Seattle, Vashington, with his wile, Athalie. Piioi to moving to the US,
he iesiueu in the small state ol Tasmania in Austialia. Outsiue ol technologym Rowan`s
passions incluue snowLoaiuing, mountain Liking, hoise iiuing, iock climLing, anu
pietty much anything else that involves Leing active. The piimaiy locus ol his lile,
howevei, is to lollow ]esus.

S-ar putea să vă placă și