Free Trial

Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.


  • Create BookmarkCreate Bookmark
  • Create Note or TagCreate Note or Tag
  • DownloadDownload
  • PrintPrint
Share this Page URL
Help

Setting the State for Multiple Entities ... > Setting the State of Entities in a G...

Setting the State of Entities in a Graph

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 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 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 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);
}

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

SQL statements during save after setting state for each entity

Figure 4-3. SQL statements during save after setting state for each entity

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.