Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.
We’re going to start by looking at an example where we iterate
through the graph using our knowledge of the model and set the state for
each entity throughout the graph, or painting the
state. In the next section you’ll see how you can generalize
this solution so that you don’t have to manually navigate the graph set
the state of each entity. We’re going to write a method that will save a
Destination and its related Lodgings. Deleted entities are tricky in
disconnected scenarios. If you delete the entity on the client side,
there’s nothing to send to the server so that it knows to delete that
data in the database as well. This example demonstrates one pattern for
overcoming the problem. Add the SaveDestinationAndLodgings method shown in
Example 4-12.
Example 4-12. Setting state for each entity in a graph
private static voidSaveDestinationAndLodgings(Destinationdestination,List<Lodging> deletedLodgings) {// TODO: Ensure only Destinations & Lodgings are passed inusing(varcontext =newBreakAwayContext()) { context.Destinations.Add(destination);if(destination.DestinationId > 0) { context.Entry(destination).State =EntityState.Modified; }foreach(varlodgingindestination.Lodgings) {if(lodging.LodgingId > 0) { context.Entry(lodging).State =EntityState.Modified; } }foreach(varlodgingindeletedLodgings) { context.Entry(lodging).State =EntityState.Deleted; } context.SaveChanges(); } }
The new method accepts the Destination to be saved. This Destination may also have Lodgings related to it. The method also
accepts a list of Lodgings to be
deleted. These Lodgings may or may
not be in the Lodgings collection of
the Destination that is being saved.
You’ll also notice a TODO to ensure that the client calling the method
only supplied Destinations and
Lodgings, because that is all that
our method is expecting. If the caller were to reference an unexpected
InternetSpecial from one of the
Lodgings, we wouldn’t process this
with our state setting logic. Validating input is good practice and
isn’t related to the topic at hand, so we’ve left it out for
clarity.
The code then adds the root Destination to the context, which will cause
any related Lodgings to also be
added. Next we are using a check on the key property to determine if
this is a new or existing Destination. If the key is set to zero, it’s
assumed it’s a new Destination and
it’s left in the added state; if it has a value, it’s marked as a
modified entity to be updated in the database. The same process is then
repeated for each of the Lodgings
that is referenced from the Destination.
Finally the Lodgings that are
to be deleted are registered in the Deleted state. If these Lodgings are still referenced from the
Destination, they are already in the
state manager in the added state. If they were not referenced by the
Destination, the context isn’t yet
aware of them. Either way, changing the state to Deleted will register them for deletion. With
the state appropriately set for every entity in the graph, it’s time to
call SaveChanges.
To see the SaveDestinationAndLodgings method in action,
add the TestSaveDestinationAndLodgings method shown in
Example 4-13.
Example 4-13. Method to test SaveDestinationAndLodging method
private static voidTestSaveDestinationAndLodgings() {Destinationcanyon;using(varcontext =newBreakAwayContext()) { canyon = (fromdincontext.Destinations.Include(d => d.Lodgings)whered.Name =="Grand Canyon"selectd).Single(); } canyon.TravelWarnings ="Carry enough water!"; canyon.Lodgings.Add(newLodging{ Name ="Big Canyon Lodge"});varfirstLodging = canyon.Lodgings.ElementAt(0); firstLodging.Name ="New Name Holiday Park";varsecondLodging = canyon.Lodgings.ElementAt(1);vardeletedLodgings =newList<Lodging>(); canyon.Lodgings.Remove(secondLodging); deletedLodgings.Add(secondLodging); SaveDestinationAndLodgings(canyon, deletedLodgings); }
This method retrieves the Grand Canyon Destination from the database, using eager
loading to ensure that the related Lodgings are also in memory. Next it changes
the TravelWarnings property of the
canyon. Then one of the Lodgings is modified and another is removed
from the Lodgings property of the
canyon and added to a list of
Lodgings to be deleted. A new
Lodging is also added to the canyon. Finally the canyon and the list of Lodgings to be deleted are passed to the
SaveDestinationAndLodgings method. If
you update the Main method to call
TestSaveDestinationAndLodgings and
run the application, a series of SQL statements will be sent to the
database (Figure 4-3).
The first update is for the
existing Grand Canyon Destination
that we updated the TravelWarnings
property on. Next is the update for
the Lodging we changed the name of.
Then comes the delete for the
Lodging we added to the list of
Lodgings to be deleted. Finally, we
see the insert for the new Lodging we created and added to the Lodgings collection of the Grand Canyon
Destination.