In the previous chapter you saw how to get data from the database into memory. But this is only half the story. Most applications also need to make changes to that data and then push those changes back into the database. In this chapter we will take a look at how Entity Framework can be used to make changes to data. These changes fall into three main categories: adding new data, changing existing data and deleting existing data.
While looking at querying, we saw the main benefit of using an Object
Relational Mapper (ORM), like Entity Framework, is that application code is
written in terms of your object model. As you write your application, you
don’t need to be looking at the shape of your tables and columns. Nor do you
need to know how to write INSERT
,
UPDATE
, and DELETE
statements for your database. Entity
Framework will take care of translating the operations you perform on your
objects into SQL statements that will push these changes into the
database.
As you perform operations on your object instances, Entity Framework
uses its change tracker to keep track of what you have
done. When you’re ready to commit the changes to the database, you call the
SaveChanges
method. SaveChanges
will invoke the update
pipeline, which is responsible for translating the changes to
your object instances into SQL statements that are executed against your
database. If you’ve developed applications using Entity Framework’s ObjectContext
, you should be familiar with this
process.
Because Entity Framework is aware of the relationships between your
entities, if you are saving related objects, it will take care of ordering
the SQL statements to ensure changes are applied in the correct order. For
example, you may be deleting an existing Destination
and also moving the Lodgings
associated with that Destination
to a different Destination
. Entity Framework will determine that
the Lodging
records must be updated
before the Destination
record is deleted,
regardless of the order that you performed these operations in
memory.
Entity Framework allows you to make changes that affect single objects, a relationship between two objects, or an entire graph of objects. In this chapter we are going to take a look at changes affecting a single object and relationships between objects. In the next chapter we’ll take a look at some advanced scenarios where operations can affect a whole graph of objects.
There are three types of changes that can affect a single entity—adding a new entity, changing the property values of an existing entity, or deleting an existing entity. In this section you’ll learn how to make each of these changes using Entity Framework.
Adding a new object with Entity Framework is as simple as
constructing a new instance of your object and registering it using the
Add
method on DbSet
. Let’s say you wanted to add a Machu
Picchu destination to the database. The AddMachuPicchu
method shown in Example 3-1 demonstrates this.
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 code constructs a new Destination
for Machu Picchu and then calls
Add
on the Destinations
set you defined in the BreakAwayContext
. Finally, the code calls
SaveChanges
, which will take the
changes and save them to the database. We see that a single INSERT
statement is executed against our
database:
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()', N'@0 nvarchar(max) ,@1 nvarchar(max) ', @0=N'Machu Picchu',@1=N'Peru'
Notice that Entity Framework is using the mapping we supplied as
it translates the object changes into SQL. For example, we mapped the
Destination
class in our domain model
to the baga.Locations
table in the
database. Entity Framework uses this information to construct an
INSERT
statement that targets the
Locations
table. The key of Destination
is an identity column, meaning the
value is generated by the database when the record is inserted. Because
of this, Entity Framework includes some additional SQL to fetch this
newly created value after the INSERT
statement has executed. Entity Framework will then take the returned
value and assign it to the DestinationId
property of the object that was
added.
In this example we used the default constructor of our POCO class to create the new instance to be inserted. A little later in this chapter you’ll learn about change tracking proxies and how to create new instances of proxies to insert.
Changing existing objects is as simple as updating the value
assigned to the property(s) you want changed and calling SaveChanges
. Perhaps we want to change the
Grand Canyon Destination
and assign a
Description
to it so that BAGA
customers know just how grand it is. Add the ChangeGrandCanyon
method shown in Example 3-2.
private static void
ChangeGrandCanyon() {using
(var
context =new
BreakAwayContext
()) {var
canyon = (from
din
context.Destinationswhere
d.Name =="Grand Canyon"
select
d).Single(); canyon.Description ="227 mile long canyon."
; context.SaveChanges(); } }
This code uses a LINQ query to load the Grand Canyon Destination
into memory. It then assigns the
new value to the Description
property
of the loaded Destination
. With the
changes completed it calls SaveChanges
, which issues an UPDATE
statement to the database:
exec sp_executesql N' update [baga].[Locations] set [Description] = @0 where ([LocationID] = @1) ',N'@0 nvarchar(500),@1 int', @0=N'227 mile long canyon.',@1=1
Again we see Entity Framework using the mapping to construct the
appropriate SQL statement. This time it’s an UPDATE
statement against the baga.Locations
table. Entity Framework uses
the key of the entity to identify the record to be updated. In our case,
that’s DestinationId
, which is mapped
to the LocationId
column. This
results in a WHERE
clause that
filters based on the LocationId
column with the value from the DestinationId
property in the object being
updated.
To delete an entity using Entity Framework, you use the Remove
method on DbSet
. Remove
works for both existing and newly added
entities. Calling Remove
on an entity
that has been added but not yet saved to the database will cancel the
addition of the entity. The entity is removed from the change tracker
and is no longer tracked by the DbContext
. Calling Remove
on an existing entity that is being
change-tracked will register the entity for deletion the next time
SaveChanges
is called.
You may be wondering why the Entity Framework team chose to call
the method Remove
rather than
Delete
, and for that matter, why
they chose Add
instead of Insert
. The names were chosen for
consistency with other collections and sets in the .NET Framework.
Other collections all use the Add
/Remove
pair of methods to bring elements
into and out of the collection.
Let’s add a DeleteWineGlassBay
method that will delete the Wine Glass Bay Destination
from our database (Example 3-3).
private static void
DeleteWineGlassBay() {using
(var
context =new
BreakAwayContext
()) {var
bay = (from
din
context.Destinationswhere
d.Name =="Wine Glass Bay"
select
d).Single(); context.Destinations.Remove(bay); context.SaveChanges(); } }
The code uses a LINQ query to load the Wine Glass Bay Destination
from the database. It then calls
Remove
on the Destinations
set you have defined on the
BreakAwayContext
. Now that Wine Glass
Bay is registered for deletion (at least from our database), we call
SaveChanges
and a DELETE
statement is run against our
database:
exec sp_executesql N' delete [baga].[Locations] where ([LocationID] = @0)', N'@0 int', @0=3
This is a very simple DELETE
statement that uses the key value from the Wine Glass Bay object to
build a WHERE
clause that identifies
the object we are deleting.
DbSet.Remove
follows the same
rule that we’ve always had to contend with for having Entity Framework
deleting objects. The object must be tracked by the change tracker and
marked as Deleted
in order for
SaveChanges
to construct a DELETE
command to send to the
database.
If you know you need to delete an entity, but it’s not already in memory, it’s a little inefficient to retrieve that entity from the database just to delete it. If you know the key of the entity you want to delete, you can attach a stub that represents the entity to be deleted, and then delete this stub. A stub is an instance of an entity that just has the key value assigned. The key value is all that’s required for deleting entities.
When attaching a stub you use the DbSet.Attach
method to let Entity Framework
know that it’s an existing entity. Once an entity is attached, it
behaves just like an entity that was retrieved from the database. So
calling DbSet.Remove
will cause a
DELETE
statement to be sent to the
database during SaveChanges
. For
example, the following code would delete the Destination with an ID of
2, without loading it from the database:
var
toDelete =new
Destination
{ DestinationId = 2 }; context.Destinations.Attach(toDelete); context.Destinations.Remove(toDelete); context.SaveChanges();
If an entity has been loaded into memory, you won’t be able to
attach a stub for the entity. Doing so would cause two existing
entities with the same key value to be tracked by the context. If you
try and do this you will get an InvalidOperationException
stating “An object
with the same key already exists in the ObjectStateManager.”
Another way to delete entities without loading them is to use
DbContext.Database.ExecuteSqlCommand
to
execute some raw SQL to perform the deletion in the database. For
example, the following code would delete the Hawaii Destination
without loading it from the database:
context.Database.ExecuteSqlCommand(
"DELETE FROM baga.Locations WHERE LocationName = 'Hawaii'"
);
Because we are using raw SQL, we are bypassing any mapping that
is done using Entity Framework. In the above code we needed to
remember that the Destination
class
is mapped to the baga.Locations
table and that the Name
property is
mapped to the LocationName
column.
If you are deleting objects that have related data, you may need
to update related data for the delete to succeed. The required updates
to related data will depend on whether the relationship is optional or
required. Optional relationships mean that the child entity can exist
in the database without a parent assigned. For example, a Reservation
can exist in the database
without a Trip
assigned. Required
relationships mean the child entity cannot exist without a parent
assigned. For example, a Lodging
cannot exist in the database without being assigned to a Destination
.
If you delete an entity that is the parent of an optional relationship, the relationship between the parent and any child entities can be deleted, too. This means the child entities will be updated so that they are no longer assigned to a parent—the foreign key column in the database will be set to null. Entity Framework will automatically delete the relationship for you if the child entity has been loaded into memory from the database.
Let’s start by seeing what happens if we delete a parent entity
of an optional relationship when the child entity isn’t loaded into
memory. There is an optional relationship between a Reservation
and a Trip
: Trip
is the parent and Reservation
is the child. Add a method that
tries to delete a Trip
without its
child Reservation
loaded into
memory (Example 3-4).
private static void
DeleteTrip() {using
(var
context =new
BreakAwayContext
()) {var
trip = (from
tin
context.Tripswhere
t.Description =="Trip from the database"
select
t).Single(); context.Trips.Remove(trip); context.SaveChanges(); } }
If you update the Main
method
to call DeleteTrip
and run the
application, you will get a DbUpdateException
informing you that there
was an error while saving. If you drill into the InnerException
properties, you will see that
the innermost exception is a SqlException
stating that “The DELETE
statement conflicted with the REFERENCE constraint
‘FK_Reservations_Trips_Trip_Identifier’.” You get this exception
because there is still a Reservation
in the
database that has a foreign key pointing to the Trip
you are trying to delete. Let’s update
the DeleteTrip
method so that the
Reservation
that references the
Trip
we are deleting is loaded into
memory (Example 3-5).
private static void
DeleteTrip() {using
(var
context =new
BreakAwayContext
()) {var
trip = (from
tin
context.Tripswhere
t.Description =="Trip from the database"
select
t).Single();var
res = (from
rin
context.Reservationswhere
r.Trip.Description =="Trip from the database"
select
r).Single(); context.Trips.Remove(trip); context.SaveChanges(); } }
The updated code uses a second LINQ query to load the single
Reservation
that is assigned to the
Trip
that is being deleted. If you
run the application again, it will succeed and two SQL statements are
sent to the database (Figure 3-1).
The update statement sets the foreign key column of the
Reservation
to null, so that it is no longer
related to the Trip
that is being deleted. The
delete statement then deletes the Trip
from the
database.
Required relationships are a bit different because the foreign
key column in the database can’t be set to null. If you delete a
parent entity, each child must either be deleted or updated to belong
to a different parent entity. You can either do this manually or have
the child entities automatically deleted, using a cascade delete.
Failure to delete or reassign child records when you delete a parent
will result in a referential integrity constraint violation when you
attempt to SaveChanges
.
In our model there is a cascade delete defined between Lodging
and Destination
. If we delete a Destination
, the Lodging
instances that are assigned to it
will get automatically deleted. In our database there is a Grand
Canyon Destination
, which has two
related Lodging
s. Add the DeleteGrandCanyon
method shown in Example 3-6.
private static void
DeleteGrandCanyon() {using
(var
context =new
BreakAwayContext
()) {var
canyon = (from
din
context.Destinationswhere
d.Name =="Grand Canyon"
select
d).Single(); context.Entry(canyon) .Collection(d => d.Lodgings) .Load(); context.Destinations.Remove(canyon); context.SaveChanges(); } }
The code loads the Grand Canyon Destination
from the database and then uses
eager loading to ensure the related Lodging
s are also loaded into memory. The
code then marks the Grand Canyon for deletion and pushes the changes
to the database. If you update the Main
method to call DeleteGrandCanyon
and run the application,
three SQL commands get sent to the database (Figure 3-2).
Because the related Lodging
s were loaded into
memory, and we had a cascade delete rule configured, Entity Framework
has automatically deleted the related Lodging
s. The
first two delete
statements are
deleting the related Lodging
s and
the final delete
statement deletes
the Grand Canyon Destination
.
Cascade delete is also configured in the database, so we don’t
need to load the related data for it to be automatically deleted.
Modify the DeleteGrandCanyon
method
so that it no longer loads the related Lodging
s into memory (Example 3-7).
private static void
DeleteGrandCanyon() {using
(var
context =new
BreakAwayContext
()) {var
canyon = (from
din
context.Destinationswhere
d.Name =="Grand Canyon"
select
d).Single(); context.Destinations.Remove(canyon); context.SaveChanges(); } }
If you run the application again, a single delete
command is sent to the database, to
delete the Grand Canyon Destination
. Because there is a cascade
delete configured on the foreign key constraint between
Lodging
and Destination
, the
database has taken care of deleting the related data. Cascade delete
is covered in detail in Programming
Entity Framework, 2e.
If we didn’t have a cascade delete defined, we would need to
manually mark each of the related Lodging
entities as deleted before
attempting to save. To mark each of the related Lodgings
as deleted, you would need to
iterate through the Lodgings
property and call DbSet.Remove
on
each Lodging
. Because Entity
Framework is going to update the Lodgings
property to remove each Lodging
as it is marked for deletion, you
need to use ToList
to create a copy
of the Lodgings
. Failure to create
a copy will result in the contents of Lodgings
changing as it is iterated, which
is not supported by the .NET Framework:
foreach
(var
lodgingin
canyon.Lodgings.ToList()) { context.Lodgings.Remove(lodging); }
An alternative to deleting the child entities is to assign them
to a new parent. For example, we could move the Lodging
s from Grand Canyon to Hawaii before
deleting the Grand Canyon:
foreach
(var
lodgingin
canyon.Lodgings.ToList()) { lodging.Destination = hawaii; }
You’ll learn more about changing relationships in Changing a Relationship Between Objects.
So far we have looked at making a single change followed by a call
to SaveChanges
to push that change to
the database. Your application may want to intersperse many queries and
changes and then push all the changes to the database at once. Let’s add
a MakeMultipleChanges
method that
does just this (Example 3-8).
private static void
MakeMultipleChanges() {using
(var
context =new
BreakAwayContext
()) {var
niagaraFalls =new
Destination
{ Name ="Niagara Falls"
, Country ="USA"
}; context.Destinations.Add(niagaraFalls);var
wineGlassBay = (from
din
context.Destinationswhere
d.Name =="Wine Glass Bay"
select
d).Single(); wineGlassBay.Description ="Picturesque bay with beaches."
; context.SaveChanges(); } }
The code creates a new Destination
for Niagara
Falls and adds it to the Destinations
set. It then retrieves the Wine Glass Bay Destination
and changes its description. Once
these changes are made, SaveChanges
is called to push the changes to the database. If you update the Main
method to call MakeMultipleChanges
and run the application you will see three statements are run against
the database (Figure 3-3).
The first is a SELECT
statement
to fetch the Wine Glass Bay Destination
. The next two statements are an
UPDATE
and an INSERT
that are run when we call SaveChanges
.
SaveChanges
is transactional,
meaning that it either pushes all the changes to the database or none
of them. If one change fails, any changes that have already been made
are rolled back and the database is left in the state it was in before
SaveChanges
was called. You can
learn more about how Entity Framework uses transactions by default and
how to override that default behavior in Chapter 20 of Programming
Entity Framework, 2e.
You may have noticed that DbSet.Add
returns an object. It returns the
same object that you pass into the method. This may seem a little
strange at first, but it enables a nice coding pattern that you may find
convenient to use in your applications. Your application might allow a user to search
for a Person
based on his or her SocialSecurity
Number
.
If the Person
is found, your code can
use the existing entity. But if the Person
isn’t located, you want to create a new
Person
with the supplied SocialSecurityNumber
. The FindOrAddPerson
method shown in Example 3-9 demonstrates this
pattern.
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); } }
Remember that DbSet.Find
is an
easy way to locate entities based on their key values. If it finds the
entity either in memory or in the database, it will return the correct
instance. But if it can’t locate the entity, Find
will return null
. You can combine
Find
with the ??
operator that allows you to provide an
alternate value to return if some code returns null
. In the example above, we attempt to
locate the entity by using Find
based
on its key. If Find
doesn’t locate
the entity, we add a new Person
to
the context instead.
The ??
operator is specific to
C#; however, the same logic can be written in VB.NET using the If
method:
Dim
person =If
(context.People.Find(ssn), context.People.Add(New
Person
()With
{ .SocialSecurityNumber = ssn, .FirstName ="<enter first name>"
, .LastName ="<enter last name>"
} ))
Now that you know how to add, change, and delete entities, it’s time to look at how we change relationships between those entities. Your domain model exposes relationships using navigation properties and, optionally, a foreign key property. Changing a relationship is achieved by changing the values assigned to those properties.
Given that a relationship can be represented by up to three
properties (two navigation properties and a foreign key property), you may
be wondering if you need to update all three just to change the
relationship. Updating just one of these properties is enough to let
Entity Framework know about the change. It is also fine to update more
than one of the properties if you want to, provided that the changes
represent the same change. When you call SaveChanges
, Entity Framework will take care of
updating the rest of these properties for you; this is known as
relationship fix-up. Rather than waiting for SaveChanges
to fix up the properties, you can
trigger this fix-up on demand by calling DetectChanges
or have it happen in real-time by
using change tracking proxies. Both of these concepts are described later
in this chapter.
While the basics of changing relationships are quite simple, there are a lot of intricate details to be familiar with as you get into more advanced relationship scenarios. These intricacies are not specific to the DbContext API and are well beyond the scope of this book.
You can find a detailed look at relationships in Chapter 19 of Programming Entity Framework, 2e.
To add a new relationship, you need to assign one of the objects
in the relationship to the navigation property of the other object. If
the navigation property you want to change is a reference (for example,
the Destination
property of the
Resort
class), you set the value to
the related object. If the navigation property is a collection (for
example, the Payments
property of the
Reservation
class), you use the
Add
method to add it to that
collection. Remember that the change can be made at one or both ends of
the relationship.
Let’s assume you want to add a Lodging
record for a new luxury resort that is
opening, and you want to associate it with the Grand Canyon. To follow
along, add the NewGrandCanyonResort
method shown in Example 3-10.
private static void
NewGrandCanyonResort() {using
(var
context =new
BreakAwayContext
()) {var
resort =new
Resort
{ Name ="Pete's Luxury Resort"
}; context.Lodgings.Add(resort);var
canyon = (from
din
context.Destinationswhere
d.Name =="Grand Canyon"
select
d).Single(); canyon.Lodgings.Add(resort); context.SaveChanges(); } }
This code creates the new Resort
and adds it to the Lodgings
set we defined on BreakAwayContext
(remember that Resort
derives from Lodging
). Next, the code locates the Grand
Canyon Destination
that we want to
add this new Resort
to. Then the new
Resort
is added to the Lodgings
collection of the Grand Canyon. This
lets Entity Framework know that the two objects are related. SaveChanges
is then used to push these changes
to the database.
In Example 3-10, we added the
new Resort
to the Lodgings
set and then added it to the
canyon.Lodgings
collection. The
example is intentionally redundant for clarity as you learn about the
behaviors. The first call ensures that the context knows that resort
is new and needs to be inserted into
the database. The second call then specifies that resort
must also be related to canyon
. While this makes it obvious that
resort
is a new entity, adding it
twice is not strictly necessary in this particular example. If you had
skipped adding the new resort
to
the context and only added it to canyon
, Entity Framework would have found
resort
because it is now referenced
from the navigation property of an entity that is tracked by the
context (canyon
). Entity Framework
would have recognized that resort
was not being tracked and in response would have assumed the resort
needed to be in the Added
state. Therefore we could have left
out the line of code that added resort
to context.Lodgings
and achieved the same
result.
In the code we updated the collection end of a one-to-many
relationship. But remember we can update either end of the relationship.
Rather than adding the new Resort
to
the Destination.Lodgings
collection,
we could have set the Lodging.Destination
property to the desired
Destination
instance:
resort.Destination = canyon;
Lodging
also exposes a foreign
key property, DestinationId
, to
represent the relationship. We could also have updated that
instead:
resort.DestinationId = canyon.DestinationId;
If you are adding an object to a collection navigation property,
you need to make sure that the collection property will be initialized.
Remember that, by default, properties will be assigned a value of
null
. In our case we enabled lazy
loading on the Lodgings
property, so
Entity Framework took care of creating a collection and assigning it to
the Lodgings
property. If you aren’t
using lazy loading, you will need to include logic in either your
classes or the consuming code to initialize the collection.
Changing a relationship is actually the same as adding a new relationship. When we add a relationship, we are changing it from “unassigned” to point to an entity. Changing a relationship to point from one entity to another uses exactly the same process. To change a relationship, we locate the entity to be changed and update the navigation property, or foreign key property.
Perhaps we made a mistake while entering some data and the Grand
Hotel actually exists at the Great Barrier Reef rather than the Grand
Canyon. The ChangeLodgingDestination
method shown in Example 3-11
demonstrates assigning a new relationship that will replace an existing
relationship.
private static void
ChangeLodgingDestination() {using
(var
context =new
BreakAwayContext
()) {var
hotel = (from
lin
context.Lodgingswhere
l.Name =="Grand Hotel"
select
l).Single();var
reef = (from
din
context.Destinationswhere
d.Name =="Great Barrier Reef"
select
d).Single(); hotel.Destination = reef; context.SaveChanges(); } }
The code locates both the Grand Hotel and the Great Barrier Reef
by using LINQ queries. Next it updates the relationship by changing the
Destination
property of the Grand
Hotel to point to the Great Barrier Reef. There is no need to remove the
existing relationship between the Grand Hotel and the Grand Canyon.
Entity Framework knows that we want the relationship to be updated,
which implies it will no longer point to the old value. Running the code
will result in a single update
statement being sent to the database:
exec sp_executesql N'update [dbo].[Lodgings] set [destination_id] = @0 where ([LodgingId] = @1) ',N'@0 int,@1 int',@0=4,@1=1
The update statement looks very similar to the one you saw earlier
in this chapter, when we modified a String
property of an entity. Entity Framework
uses the Location
s key value to
locate the record to be updated. This time, instead of updating a simple
column, it is updating the foreign key column to point to the primary
key of the Destination
we updated our
Location
to.
By now you’ve probably worked out that we could also make the
change by adding hotel
to the Lodgings
property of reef
:
reef.Lodgings.Add(hotel);
We could also make the change by setting the foreign key property:
hotel.DestinationId = reef.DestinationId;
Let’s say that Dave is no longer the primary contact for Dave’s
Dump. In fact, the service is so bad at this lodging that they no longer
have a contact at all. This means we simply want to remove the
relationship rather than changing it to a new Person
.
To remove a relationship, you can remove the target object from a
collection navigation property. Alternatively, you can set a reference
navigation property to null
. If your
classes expose a nullable foreign key property for the relationship, a
third option is to set the foreign key to null
.
Removing relationships by changing the foreign key is only
possible with nullable properties (for example, the
PrimaryContactId
property of the
Lodging
class). If your foreign key is an int
(for example, the
DestinationId
property of the
Lodging
class), you won’t be able to set the value
to null and by convention, that relationship would be required.
Setting the value to 0 would cause a primary key/foreign key
constraint error in the database, as there will be no parent whose
primary key is equal to 0.
Add the RemovePrimaryContact
method shown in Example 3-12.
private static void
RemovePrimaryContact() {using
(var
context =new
BreakAwayContext
()) {var
davesDump = (from
lin
context.Lodgingswhere
l.Name =="Dave's Dump"
select
l).Single(); context.Entry(davesDump) .Reference(l => l.PrimaryContact) .Load(); davesDump.PrimaryContact =null
; context.SaveChanges(); } }
The code starts by fetching Dave’s Dump from the database with a
LINQ query. Next it loads the related PrimaryContact
. We need to do this so that
something is actually changing when we set the value to null
. Because lazy loading isn’t enabled for
this property it is already null by default so the code explicitly loads
the PrimaryContact
. Eager loading or
enabling lazy loading would achieve exactly the same thing. Once the
contact is loaded, the PrimaryContact
property is set to null
to let Entity
Framework know that we want to delete this relationship. Dave will not
be deleted from the database, nor will Dave’s Dump, but they will no
longer be related to each other. SaveChanges
will update the database to null
out the foreign key for the primary contact of Dave’s Dump.
This highlights one of the advantages of exposing foreign key
properties in your classes. Because we expose a foreign key property for
the PrimaryContact relationship, and that property used a nullable
integer (Nullable<int>
or
int?
), then we can set that property
to null
without the need to load the
related data. This works because foreign key properties are always
populated when you query for an entity, whereas navigation properties
are only populated when you load the related data. Let’s rewrite the
RemovePrimaryContact
method to modify
the foreign key property rather than the navigation property (Example 3-13).
private static void
RemovePrimaryContact() {using
(var
context =new
BreakAwayContext
()) {var
davesDump = (from
lin
context.Lodgingswhere
l.Name =="Dave's Dump"
select
l).Single(); davesDump.PrimaryContactId =null
; context.SaveChanges(); } }
In this example, we were removing an optional relationship;
according to the model we built, it is fine for Lodging
to exist without a PrimaryContact
. If we tried to remove a
required relationship the outcome would be a little different. Let’s say
we had tried to remove the relationship between Dave’s Dump and the
Grand Canyon. According to the BAGA model, Lodging
can’t exist without a Destination
. Entity Framework would allow us
to set the Lodging.Destination
property to null, but it would throw an exception when we tried to
SaveChanges
. We are allowed to set
the property to null provided we set it to another valid Destination
before we try and SaveChanges
.
Given that Dave is no longer a contact, we may want to remove him from the database, rather than just removing his relationship as a primary contact for Dave’s Dump. If you delete an entity there is no need to delete all the relationships that they participate in: Entity Framework will automatically delete them for you. As discussed in Deleting Existing Entities, if any of the relationships are required you will need to delete or reassign any child entities before saving.
Throughout this chapter you have seen that Entity Framework keeps
track of the changes you make to your objects. Entity Framework uses its
change tracker to do this. You can access the change
tracker information, and some change tracking–related operations, through
the DbContext.ChangeTracker
property.
You’ll see more about the Change Tracker API in Chapter 5.
There are two different ways that Entity Framework can track the changes to your objects: snapshot change tracking or change tracking proxies.
The code written so far in this chapter has relied on snapshot
change tracking. The classes in our model are all POCO and they
don’t contain any logic to notify Entity Framework when a property
value is changed. Because there is no way to be notified when a
property value changes, Entity Framework will take a snapshot of the
values in each property when it first sees an object and store the
values in memory. This snapshot occurs when the object is returned
from a query or when we add it to a DbSet
. When Entity Framework needs to know
what changes have been made, it will scan each object and compare
its current values to the snapshot. This process of scanning each
object is triggered through a method of ChangeTracker
called DetectChanges
.
The other mechanism for tracking changes is through change tracking proxies, which allow Entity Framework to be notified of changes as they are made. In Chapter 2, you learned about dynamic proxies that are created for lazy loading. Change tracking proxies are created using the same mechanism, but in addition to providing for lazy loading, they also have the ability to communicate changes to the context.
To use change tracking proxies, you need to structure your
classes in such a way that Entity Framework can create a dynamic
type at runtime that derives from our POCO class and override every
property. This dynamic type, known as a dynamic proxy, includes
logic in the overridden properties to notify Entity Framework when
those properties are changed. In fact, all of the rules for creating
dynamic change tracking proxies from POCOs that you learned about if
you read Programming
Entity Framework, 2e, are the same when you are using POCOs
with DbContext
.
Snapshot change tracking depends on Entity Framework being able to
detect when changes occur. The default behavior of the DbContext API is to
automatically perform this detection as the result of many events on the
DbContext
. DetectChanges
not only updates the context’s
state management information so that changes can be persisted to the
database, it also performs relationship fix-up when you have a combination
of reference navigation properties, collection navigation properties and
foreign keys. It’s important to have a clear understanding of how and when
changes are detected, what to expect from it and how to control it. This
section addresses those concerns.
The DetectChanges
method of
ObjectContext
has been available
since Entity Framework 4 as part of the snapshot change tracking pattern
on POCO objects. What’s different about DbContext.ChangeTracker.DetectChanges
(which
in turn, calls ObjectContext.DetectChanges
) is that there are
many more events that trigger an automatic call to DetectChanges
. Here is the list of the method
calls you should already be familiar with that will cause DetectChanges
to do its job:
DbSet.Add
DbSet.Find
DbSet.Remove
DbSet.Local
DbContext.SaveChanges
Running any LINQ query against a DbSet
There are more methods that will trigger DetectChanges
. You’ll learn more about these
methods throughout the rest of this book:
DbSet.Attach
DbContext.GetValidationErrors
DbContext.Entry
DbChangeTracker.Entries
The most obvious time that Entity Framework needs to know about
changes is during SaveChanges
, but
there are also many others. For example, if we ask the change tracker
for the current state of an object, it will need to scan and check if
anything has changed. Scanning isn’t just restricted to the object in
question either. Consider a situation where you query for a Lodging
from the database and then add it to
the Lodgings
collection of a new
Destination
. This Lodging
is now modified because assigning it
to a new Destination
changes its DestinationId
property. But to know that this
change has occurred (or hasn’t occurred) Entity Framework needs to scan
all of the Destination
objects as
well. Many of the operations you perform on the DbContext
API will cause DetectChanges
to be run.
In most cases DetectChanges
is
fast enough that it doesn’t cause performance issues. However, if you
have a very large number of objects in memory or you are performing a
lot of operations on DbContext
in
quick succession, the automatic DetectChanges
behavior may be a performance
concern. Fortunately you have the option to switch off the automatic
DetectChanges
behavior and call it
manually when you know that it needs to be called.
Entity Framework is built on the assumption that you will call
DetectChanges
before every API call
if you have changed any of the entities since the last API call. This
includes calling DetectChanges
before
running any queries. Failure to do this can result in unexpected side
effects. DbContext
takes care of this
requirement for you provided that you leave automatic DetectChanges
enabled. If you switch it off,
you are responsible for calling DetectChanges
.
Working out when DetectChanges
needs to be called isn’t as
trivial as it may appear. The Entity Framework team strongly
recommends that you only swap to manually calling DetectChanges
if you are experiencing
performance issues. It’s also recommended to only opt out of automatic
DetectChanges
for poorly performing
sections of code and to reenable it once the section in question has
finished executing.
Automatic DetectChanges
can be
toggled on and off via the DbContext.Configuration.AutoDetectChangesEnabled
Boolean flag. Let’s add a ManualDetectChanges
method that disables
automatic DetectChanges
and observes
the effect this has (Example 3-14).
private static void
ManualDetectChanges() {using
(var
context =new
BreakAwayContext
()) { context.Configuration.AutoDetectChangesEnabled =false
;var
reef = (from
din
context.Destinationswhere
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();Console
.WriteLine("After DetectChanges: {0}"
, context.Entry(reef).State); } }
The code switches off automatic DetectChanges
and then queries for the Great
Barrier Reef and changes its description. The next line will write out
the current state that the context thinks the reef
entity is in. We then manually call
DetectChanges
and repeat the process
of writing out the current state. Accessing the current state makes use
of the Entry
method from Change
Tracker API, which is discussed in Chapter 5. If you update the Main
method to call ManualDetectChanges
, you will see the
following output:
Before DetectChanges: Unchanged After DetectChanges: Modified
As expected, the context doesn’t detect that the reef
entity is modified until after we
manually call DetectChanges
. The
reason we get an incorrect result is that we broke the rule of calling
DetectChanges before calling an API after we had modified an entity.
Because we were simply reading the state of an entity, this didn’t have
any nasty side effects.
The code we saw in Example 3-9 didn’t really buy
us anything by switching off automatic DetectChanges
. Calling the DbContext.Entry
method would also have
automatically triggered DetectChanges
if the change tracking wasn’t disabled.
If you write tests to check the state of entities and you use
this Entry
method to inspect state,
keep in mind that the Entry
method
itself calls DetectChanges
. This
could inadvertently alter your test results. You can use the AutoDetectChangesEnabled
configuration to
have tighter control over DetectChanges
in this scenario.
However if we were performing a series of API calls on DbContext
without changing any objects in
between, we could avoid some unnecessary execution of the DetectChanges
process. The AddMultipleDestinations
method shown in Example 3-15 demonstrates
this.
private static void
AddMultipleDestinations() {using
(var
context =new
BreakAwayContext
()) { context.Configuration.AutoDetectChangesEnabled =false
; context.Destinations.Add(new
Destination
{ Name ="Paris"
, Country ="France"
}); context.Destinations.Add(new
Destination
{ Name ="Grindelwald"
, Country ="Switzerland"
}); context.Destinations.Add(new
Destination
{ Name ="Crete"
, Country ="Greece"
}); context.SaveChanges(); } }
This code avoids four unnecessary calls to DetectChanges
that would have occurred while
calling the DbSet.Add
and SaveChanges
methods. This example is used
purely for demonstration purposes and is not a scenario where disabling
DetectChanges
is going to provide any
significant benefit. In Chapter 5 you’ll
learn about making changes to your objects using the Change Tracker API.
The Change Tracker API enables you to make changes to your objects by
going through a DbContext API. This approach allows you to change your
objects without the need to call DetectChanges
.
DetectChanges
is also
responsible for performing relationship fix-up for any relationships
that it detects have changed. If you have changed some relationships and
would like to have all the navigation properties and foreign key
properties synchronized, DetectChanges
will achieve this. This can be
particularly useful in data-binding scenarios where your UI will change
one of the navigation properties (or perhaps the foreign key property)
but you then want the other properties in the relationship to be updated
to reflect the change. The DetectRelationshipChanges
method in Example 3-16 uses DetectChanges
to perform relationship
fix-up.
private static void
DetectRelationshipChanges() {using
(var
context =new
BreakAwayContext
()) {var
hawaii = (from
din
context.Destinationswhere
d.Name =="Hawaii"
select
d).Single();var
davesDump = (from
lin
context.Lodgingswhere
l.Name =="Dave's Dump"
select
l).Single(); context.Entry(davesDump) .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 code loads the Hawaii Destination
into memory as well as Dave’s Dump
Lodging
. It also uses explicit
loading to load the Destination
of
Dave’s Dump—that’s the Grand Canyon. Dave’s Dump has such a bad
reputation that he has decided to move the Dump to Hawaii where nobody’s
heard of him yet. So we add the davesDump
instance to the Lodgings
collection of hawaii
. Because we are using POCO objects,
Entity Framework doesn’t know that we’ve made this change, and therefore
it doesn’t fix up the navigation property or foreign key property on
davesDump
. We could wait until we
call SaveChanges
, or any other
method, which triggers DetectChanges
,
but perhaps we want things fixed up right away. We’ve added in a call to
DetectChanges
to achieve this. If we
update the Main
method to call
DetectRelationshipChanges
and run the
application we see this in action:
Before DetectChanges: Grand Canyon After DetectChanges: Hawaii
Before the DetectChanges
call,
Dave’s Dump is still assigned to the old Destination
. After we call DetectChanges
, relationship fix-up has
occurred and everything is back in sync.
If your performance profiler has pinpointed excessive calls to
DetectChanges
as a problem or you
prefer relationship fix-up to occur in real time, there is another
option—the change tracking proxies mentioned earlier. With only some minor
changes to your POCO classes, Entity Framework will be able to create
change tracking proxies. Change tracking proxies will allow Entity
Framework to track changes as we make them to our objects and also perform
relationship fix-up as it detects changes to relationships.
The rules for allowing a change tracking proxy to be created are as follows:
The class must be public
and
not sealed
.
Each property must be marked as virtual
.
Each property must have a public getter and setter.
Any collection navigation properties must be typed as ICollection<T>
.
Update the Destination class as shown in Example 3-17 to meet these
requirements. Notice that we are also removing the logic from the
constructor that initialized the Lodgings
property. The change tracking proxy
will override any collection navigation properties and use its own
collection type (EntityCollection<TEntity>
). This
collection type will track any changes to the collection and report them
to the change tracker. If you attempt to assign another type to the
property, such as the List<T>
we
were creating in the constructor, the proxy will throw an
exception.
[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 Chapter 2, you learned how Entity Framework creates dynamic proxies for a class when one or more navigation properties in that class are marked virtual. Those proxies, which derive from the given class, allow the virtual navigation properties to be lazy loaded. The change tracking proxies are created in the same way at runtime, but these proxies have more features than those you saw in Chapter 2.
While the requirements for getting a change tracking proxy are
fairly simple, it’s also very easy to miss one of them. It’s even easier
to make a change to the class in the future that will unintentionally
break one of the rules. Because of this, it’s a good idea to add a unit
test that ensures Entity Framework can create a change tracking proxy.
Let’s add a method that will test just this (Example 3-18). You’ll also need to add
a using for the System.Data.Objects.DataClasses
namespace.
private static void
TestForChangeTrackingProxy() {using
(var
context =new
BreakAwayContext
()) {var
destination = context.Destinations.First();var
isProxy = destinationis
IEntityWithChangeTracker
;Console
.WriteLine("Destination is a proxy: {0}"
, isProxy); } }
When Entity Framework creates the dynamic proxy for change tracking,
it will implement the IEntityWithChangeTracker
interface. The test in
Example 3-18 creates a Destination
instance by retrieving it from the
database and then checks for this interface to ensure that the Destination
is wrapped with a change tracking
proxy. Note that it’s not enough just to check that Entity Framework is
creating a proxy class that derives from our class, because lazy loading
proxies will also do this. The presence of IEntityWithChangeTracker
is what causes Entity
Framework to listen for changes in real time.
Now that we have a change tracking proxy, let’s update Main to call
the ManualDetectChanges
method we wrote
back in Example 3-14 and run the
application:
Before DetectChanges: Modified After DetectChanges: Modified
This time we see that Entity Framework is aware of changes
regardless of whether DetectChanges is called or not. Now update Main to
call the DetectRelationshipChanges
method we wrote in Example 3-16 and run the
application:
Before DetectChanges: Hawaii After DetectChanges: Hawaii
This time we see that Entity Framework detected the relationship
change and performed relationship fix-up without DetectChanges
being called.
It is not necessary to disable automatic DetectChanges
when you use change tracking
proxies. DetectChanges
will skip the
change detection process for any objects that report changes in real
time. Therefore, enabling change tracking proxies is enough to get the
performance benefits of avoiding DetectChanges
. In fact, Entity Framework won’t
even take a snapshot of the property values when it finds a change
tracking proxy. DetectChanges knows it can skip scanning for changes in
entities that don’t have a snapshot of their original values.
If you have entities that contain complex types (for example,
Person.Address
), Entity Framework
will still use snapshot change tracking for the properties contained in
the complex type. This is required because Entity Framework does not
create a proxy for the complex type instance. You still get the benefits
of automatic change detection on the properties defined directly on the
entity itself, but changes to properties on the complex type will only
be detected by DetectChanges
.
Entity Framework will automatically create proxies for the results
of any queries you run. However, if you just use the constructor of your
POCO class to create new objects, these will not be proxies. In order to
get proxies you need to use the DbSet.Create
method to get new instances of an
entity. This rule is the same as when working with POCOs with ObjectContext
and ObjectSet
.
If you have enabled change tracking proxies for an entity in
your model, you can still create and add nonproxy instances of the
entity. Entity Framework will happily work with a mixture of proxied
and nonproxied entities in the same set. You just need to be aware
that you will not get automatic change tracking or relationship fix-up
for instances that are not change tracking proxies. Having a mixture
of proxied and nonproxied instances in the same set can be confusing,
so it’s generally recommended that you use DbSet.Create
to create new instances so that
all entities in the set are change tracking proxies.
Add the CreatingNewProxies
method shown in Example 3-19 to see
this in action.
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
lin
context.Lodgingswhere
l.Name =="Dave's Dump"
select
l).Single(); context.Entry(davesDump) .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 code starts by creating two new Destination
instances and adding them to the
Destinations
set on the context. One
of these Destination
s is just an
instance of our POCO class. The other is created using DbSet.Create
and is a change tracking proxy.
Next, the code queries for Dave’s Dump and loads the Destination
that it currently belongs to using
the Entry
method that you learned
about in Chapter 2. We then add Dave’s
Dump to the Lodgings
property of the
POCO Destination
and then to the
proxy Destination
. At each stage the
code prints out the name of the Destination
assigned to the Destination
property 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 assigned to the Grand
Canyon Destination
. When it’s added
to the Lodgings
collection of the
nonproxy Destination
, the Destination
property on Dave’s Dump is not
updated; it’s still the Grand Canyon. Because this Destination
isn’t a proxy, Entity Framework
isn’t aware that we changed the relationship. However, when we add it to
the Lodgings
collection of the proxy
Destination
we get full relationship
fix-up instantly.
There is also a generic overload of DbSet.Create
that is used to create instances
of derived classes in our set. For example, calling Create
on the Lodgings
set will give you an instance of the
Lodging
class. But the Lodgings
set can also contain instances of
Resort
, which derives from Lodging
. To get a new proxy instance of
Resort
, we use the generic
overload:
var
newResort = context.Lodgings.Create<Resort
>();
You’ve probably gathered by now that tracking changes isn’t a trivial process and there is a bit of overhead involved. In some areas of your application, you may be displaying data in a read-only screen. Because the data will never get updated, you may want to avoid the overhead associated with change tracking.
Fortunately Entity Framework includes an AsNoTracking
method that can be used to execute
a no-tracking query. A no-tracking query is simply a
query where the results will not be tracked for changes by the context.
Add the PrintDestinationsWithoutChangeTracking
method
shown in Example 3-20.
private static void
PrintDestinationsWithoutChangeTracking() {using
(var
context =new
BreakAwayContext
()) {foreach
(var
destinationin
context.Destinations.AsNoTracking()) {Console
.WriteLine(destination.Name); } } }
The code uses the AsNoTracking
method to get a no-tracking query for the contents of the Destinations
set. The results are then iterated
over and printed to the console. Because this is a no-tracking query, the
context is not keeping track of any changes made to the Destination
s. If you were to modify a property
of one of the Destination
s and call
SaveChanges
, the changes would not be
sent to the database.
Fetching data without change tracking will usually only provide a
noticeable performance gain when you are fetching larger amounts of data
for read-only display. If your application will update and save any of
the data, you should not use AsNoTracking
there.
AsNoTracking
is an extension
method defined on IQueryable<T>
,
so you can use it in LINQ queries also. You can use AsNoTracking
on the end of the DbSet
in the from
line of the query:
var
query =from
din
context.Destinations.AsNoTracking()where
d.Country =="Australia"
select
d;
You can also use AsNoTracking
to
convert an existing LINQ query to be a no-tracking query. Note that the
code doesn’t just call AsNoTracking
on
the existing query but overrides the query
variable with the result of the AsNoTracking
call. This is required because
AsNoTracking
doesn’t modify the query
that it is called on, it returns a new query:
var
query =from
din
context.Destinationswhere
d.Country =="Australia"
select
d; query = query.AsNoTracking();
Because AsNoTracking
is an
extension method, you will need to have the System.Data.Entity
namespace imported to use
it.