Chapter 4. Working with Disconnected Entities Including N-Tier Applications

In the previous chapter you learned how to add new entities and change or delete existing entities. All the examples we looked at involved making changes one at a time to entities that are tracked by the context. Each of the changes affected a single entity or relationship. You saw that you can perform multiple of these single entity operations and then call SaveChanges to push all the changes to the database in a single transaction. In this chapter we will look at making changes to entities that are not being tracked by a context. Entities that are not being tracked by a context are known as disconnected entities.

For most single-tier applications, where the user interface and database access layers run in the same application process, you will probably just be performing operations on entities that are being tracked by a context. Operations on disconnected entities are much more common in N-Tier applications. N-Tier applications involve fetching some data on a server and returning it, over the network, to a client machine. The client application then manipulates this data before returning it to the server to be persisted.

The N-Tier pattern makes data access more complex because there is no longer a context tracking changes that are made to each entity. The data is fetched using one context, and returned to the client where there is no context to track changes. The data is then sent back to the server and must be persisted back to the database using a new instance of the context.

Note

While the majority of content in this chapter is aimed at developers writing N-Tier applications, it’s useful information for anyone working with Entity Framework and will give you a deeper understanding of how Entity Framework behaves.

When it comes time to persist the data on the server, you are typically working with a graph of entities. A graph of entities is simply a number of entities that reference each other. We’ve already worked with graphs of entities that are attached to the context. In the last chapter we looked at adding a relationship using a navigation property, which is enough to create a graph, because one entity now references another. In N-Tier scenarios this graph of entities is usually disconnected from the context, though, meaning the context isn’t yet tracking any of the entities in the graph.

When it comes time to start performing operations on this disconnected graph, there are some additional behaviors in Entity Framework that you need to be aware of. The entity that you perform the operation on is known as the root of the graph. Performing an operation on the root of disconnected graph can have side effects on the rest of the graph, too.

A Simple Operation on a Disconnected Graph

Before we delve into the complexities of N-Tier scenarios, let’s take a quick look at an example of the side effects of performing an operation on the root of a disconnected graph. In the previous chapter we saw that DbSet.Add can be used to register a new entity to be inserted when SaveChanges is called.

Note

You’ll see the term register used throughout this chapter. When an entity is registered with the context it means that the context becomes aware of the entity and starts tracking it.

So far the entities we’ve passed to the Add method have been standalone instances with no references to other entities. Now let’s see what happens when we pass the root of a newly created graph of entities that isn’t yet tracked by the context. Add the AddSimpleGraph method that is shown in Example 4-1.

Example 4-1. Method to add a graph of entities
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(
      "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 code constructs a new Destination instance, which also references two new Lodging instances in its Lodgings property. Then the new Destination is added to a context using the Add method. Once the Destination is added, the code uses the DbContext.Entry method to get access to the change tracking information that Entity Framework has about the new Destination. From this change tracking information the State property is used to write out the current state of the entity. This process is then repeated for each of the newly created Lodgings that are referenced from the new Destination. If you modify the Main method to call AddSimpleGraph and run the application you will see the following output:

Essex Destination: Added
Big Essex Hotel: Added
Essex Junction B&B: Added

It’s no surprise that Entity Framework has the new Destination registered as an Added entity because we used the Add method to add it to the context. What may be a little less obvious is that Entity Framework looked in the navigation properties of the Destination instance and saw that it referenced two Lodging instances that the context wasn’t already tracking. Entity Framework also registers these entities as Added and will insert them into the database when SaveChanges is called. The process of finding related entities is recursive, so if one of the new Lodging instances referenced a new Person instance, the Person would also get added to the context. Figure 4-1 attempts to visualize how calling Add on a disconnected Destination will also add other disconnected entities that are reachable from the Destination.

Adding a disconnected graph of entities
Figure 4-1. Adding a disconnected graph of entities

If a reference is found to an entity that is already tracked by the context, the entity that is already tracked is left in its current state. For example, if one of our new Lodging instances referenced an existing Person that had been queried for using the context, the existing Person would not be marked as Added. The existing Person would remain in the Unchanged state and the Lodging would be inserted with its foreign key pointing to the existing Person. Figure 4-2 attempts to visualize how adding a disconnected Destination will also add other disconnected entities, but if an entity that is being tracked by the context is found, it is left in its current state.

Adding a disconnected graph that references a tracked entity
Figure 4-2. Adding a disconnected graph that references a tracked entity

Exploring the Challenges of N-Tier

Now that you’ve seen how Entity Framework behaves when it is asked to add the root of a graph of entities to the context, we’re going to take a look at how this affects N-Tier applications. Remember that in an N-Tier application the data is queried for in one context, but the changes need to be persisted using another context. For example, on the server side of your application you could expose a GetDestinationAndLodgings method that will return a Destination with all of its Lodgings:

public Destination GetDestinationAndLodgings(int destinationId)

The client side of the application could then fetch a Destination and make some changes to it. These changes could include changing the Description of the Destination and adding a new Lodging to its Lodgings collection. The server side of the application could then expose a SaveDestinationAndLodgings method to push all these changes back to the database:

public void SaveDestinationAndLodgings(Destination destination)

When it comes time to implement the SaveDestinationAndLodgings method, things get a little more complicated. Like the simple graph from Example 4-1, the Destination that gets passed in isn’t tracked by a context. In fact, because it’s been serialized over the network on its way to and from the client, it’s not even the same instance that was queried for using Entity Framework. The SaveDestinationAndLodgings method needs to let Entity Framework know if it’s an existing Destination or a new Destination that needs to be added. If it’s an existing Destination, some of the properties may have been modified and therefore need to be updated in the database. The Lodgings property may also contain instances of Lodging that can also be existing or new. Since Entity Framework hasn’t been tracking these entities, you need to let it know all of this information.

Your server-side logic could be exposed through a service or perhaps a class that follows the repository pattern. Either way, you need to overcome the disconnected nature of the objects that are returned from the client application.

There are a number of different approaches to solving the challenges associated with building N-Tier applications. Covering the details of each approach is well beyond the scope of this book, but we’ll take a quick look at the different options that are available to you and how they relate to using Entity Framework.

Using Existing N-Tier Frameworks That Support Graph Modification

In a lot of cases you can save yourself the headache of dealing with the intricacies of N-Tier data access by using a framework that takes care of tracking changes that are made to data on the client and applying those changes on the server. Entity Framework is tightly integrated with WCF Data Services, which is one such framework. WCF Data Services is Microsoft’s solution to the N-Tier challenge. There are frameworks available from other vendors as well.

WCF Data Services allows you to choose what data from your model is exposed from the server and what permission clients have for the data (read, append, update, and so on). WCF Data Services also includes a client component that takes care of tracking changes you make to data on the client, pushing those changes back to the database, and saving them using your Entity Framework model. WCF Data Services uses the OData protocol (http://odata.org) for exposing data from the server; this allows your service to be accessed by clients other than WCF Data Services, including non .NET Framework platforms.

The WCF Data Services client has a similar “look and feel” to performing data access directly against DbContext. Using WCF Data Services is arguably the simplest approach to N-Tier data access with Entity Framework, and it is the approach the Entity Framework team recommends. You can learn more about using WCF Data Services with DbContext in this article: http://msdn.microsoft.com/en-us/data/hh272554.

WCF Data Services is a good option for building N-Tier applications, but there are some reasons it may not be the right tool for your job. WCF Data Services gives you quite a bit of control over how requests are processed on the server, but it is a framework and therefore it is somewhat scoped in what it can handle. Depending on your application, you may have some advanced requirements that are not supported.

You may also be in a situation where the shape of the server operations and/or the protocol to be used for client-server communication are outside of your control. In these situations, authoring your own web services may be the only viable option.

Using Explicit Operations on the Server Side

Another way to avoid the complexity of determining the changes to be made on the server is to expose very granular operations that require the client to identify the exact change to be made. For example, rather than the SaveDestinationAndLodgings method we saw earlier in this chapter, you could expose AddDestination and UpdateDestination methods. These methods would only operate on a standalone Destination instance rather than a graph of entities. You would expose separate methods for adding and updating Locations. Entity Framework makes it simple to implement these methods and you’ll find everything you need for this approach in the Understanding How DbContext Responds to Setting the State of a Single Entity section of this chapter.

While this option makes the server component of your application much easier, it potentially makes the client component more complex. You are also likely to end up with a large number of operations exposed from the server. If you can’t use an existing framework, such as WCF Data Services, you will need to weigh up the benefits of this approach and the approach covered in the next section. Granular operations will typically give you a higher quantity of code, but that code will be much simpler to write, test, and debug. Next we’ll take a look at a more generalized solution that will probably involve less code, but is inherently more complex.

Replaying Changes on the Server

Another option is to have a more generalized server operation that accepts a graph of entities and then lets Entity Framework know what state each entity in that graph should be in. This is often referred to as replaying the changes on the server, because you are walking through each entity and letting Entity Framework know what happened on the client. The process of iterating through each entity and setting its state is also known as painting the state.

There are many different ways to implement this logic. You could write code that is strongly tied to the classes in your model and each server operation knows how to navigate the graph that is passed in, look at each entity, and set its state appropriately. You can also come up with a generalized approach that can replay the changes for any graph given any root entity. The generalized approach typically uses a base class or an interface that exposes information to allow your server-side code to work out what state each entity is in. You’ll see examples of both of these in the next section of this chapter.

Note

Chapter 18 of Programming Entity Framework, 2e, demonstrated this pattern by providing a base class with a State property. The State property could be set on the client side and then read on the server side to determine how to register 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-Tier challenges and the high-level options for addressing them, it’s time to see how to implement those techniques using Entity Framework. We’ll start by taking a step back and looking at how to set the state for a single entity. Then we’ll take those techniques and use them to set the state for each entity in a graph.

Building an actual N-Tier application is beyond the scope of this book, so we’re going to keep working in the console application. We’ll implement methods that you could potentially expose from a web service for your client application to consume. Rather than serializing entities, you’ll use a temporary context to fetch any data from the database and then manipulate the data to mimic client-side changes. You’ll then pass these objects into the pseudo-server-side operations that will use a new context, with no previous knowledge of the entity instances, to persist the changes to the database.

Entity Framework has a list of states that the change tracker uses to record the status of each entity. In this section, you’ll see how to move an entity into each of these states. The states that Entity Framework uses when change tracking are the following:

Added

The entity is being tracked by the context but does not exist in the database. During SaveChanges an INSERT statement will be used to add this entity to the database.

Unchanged

The entity already exists in the database and has not been modified since it was retrieved from the database. SaveChanges does not need to process the entity.

Modified

The entity already exists in the database and has been modified in memory. During SaveChanges an UPDATE statement will be sent to the database to apply the changes. A list of the properties that have been modified is also kept to determine which columns should be set in the UPDATE statement.

Deleted

The entity already exists in the database and has been marked for deletion. During SaveChanges a DELETE statement will be used to remove the entity from the database.

Detached

The entity is not being tracked by the context.

Note

Setting an entity to the Detached state used to be important before the Entity Framework supported POCO objects. Prior to POCO support, your entities would have references to the context that was tracking them. These references would cause issues when trying to attach an entity to a second context. Setting an entity to the Detached state clears out all the references to the context and also clears the navigation properties of the entity—so that it no longer references any entities being tracked by the context. Now that you can use POCO objects that don’t contain references to the context that is tracking them, there is rarely a need to move an entity to the Detached state. We will not be covering the Detached state in the rest of this chapter.

Marking a New Entity as Added

Arguably the simplest operation is to take an entity and mark it as Added; in fact, you saw how to do that in Chapter 3. You can use the DbSet.Add method to tell Entity Framework that an entity is added. Add the AddDestination and TestAddDestination methods shown in Example 4-2.

Example 4-2. Adding a new Destination
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 method mimics a client application creating a new Destination and passing it to the AddDestination method on the server. The AddDestination method adds the new Destination to a context and then saves it to the database. If you update the Main method to call TestAddDestination and run the application, you will see that the Jackson Hole Destination is added to the database.

There is also another way to write this same method. Earlier in this chapter we saw that we could use DbContext.Entry to get access to the change tracking information for an entity. We used the State property on the change tracking information to read the state of an entity, but we can also set this property, too. Update the AddDestination method to use the State property rather than DbSet.Add (Example 4-3). You’ll need to add a using for the System.Data namespace to use the EntityState enum.

Example 4-3. Marking a Destination as added using the State property
private static void AddDestination(Destination destination)
{
  using (var context = new BreakAwayContext())
  {
    context.Entry(destination).State = EntityState.Added;
    context.SaveChanges();
  }
}

Calling DbSet.Add and setting the State to Added both achieve exactly the same thing. If the entity is not tracked by the context, it will start being tracked by the context in the Added state. Both DbSet.Add and setting the State to Added are graph operations—meaning that any other entities that are not being tracked by the context and are reachable from the root entity will also be marked as Added. If the entity is already tracked by the context, it will be moved to the Added state. So far we’ve only added entities that aren’t tracked by the context, but a little later in this chapter you’ll see that being able to set the state of an entity that is already tracked to Added is important.

Whether you choose DbSet.Add or setting the State property is simply a matter of which is more convenient in the code you are writing. For this simple scenario, the code is arguably easier to understand if you stick with DbSet.Add. Later in the chapter you’ll see that setting the State property is cleaner in generalized scenarios where your code calculates the state you are setting the entity to.

Marking an Existing Entity as Unchanged

While DbSet.Add is used to tell Entity Framework about new entities, DbSet.Attach is used to tell Entity Framework about existing entities. The Attach method will mark an entity in the Unchanged state. Add the AttachDestination and TestAttachDestination methods shown in Example 4-4.

Example 4-4. Attaching an existing Destination
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);
}

private static void AttachDestination(Destination destination)
{
  using (var context = new BreakAwayContext())
  {
    context.Destinations.Attach(destination);
    context.SaveChanges();
  }
}

The TestAttachDestination method fetches an existing Destination from the database and passes it to the AttachDestination method, which uses DbSet.Attach to register the existing Destination with a context and save the changes. You can also write this method by setting the State property for the entity to Unchanged (Example 4-5).

Example 4-5. Marking a Destination as Unchanged using the State property
private static void AttachDestination(Destination destination)
{
  using (var context = new BreakAwayContext())
  {
    context.Entry(destination).State = EntityState.Unchanged;
    context.SaveChanges();
  }
}

If you update the Main method to call TestAttachDestination and run your application, you’ll discover the AttachDestination is quite pointless because it doesn’t do anything. That’s because we told Entity Framework that the Destination was Unchanged; therefore SaveChanges just ignores the entity. While this is a bit silly if it’s all we do in the method, it will be very useful when we have a graph of entities, some of which may not need any changes pushed to the database.

DbSet.Attach and setting the State property to Unchanged have the same effect. If the entity isn’t tracked by the context, it will begin being tracked in the Unchanged state. If it is already tracked, it will be moved to the Unchanged state.

Marking an Existing Entity as Modified

You’ve seen how to mark an existing entity as Unchanged; now let’s look at existing entities that have some changes that need to be pushed to the database. There are a few options that range from marking every property as modified to telling Entity Framework what the original values were for each property and letting it calculate the modifications. For the moment we’ll just focus on getting the changes into the database by marking the whole entity as modified. You’ll learn about setting individual properties as modified in Tracking Individually Modified Properties.

When you tell Entity Framework that an entire entity is modified, it will send an update statement to the database that sets every column to the values currently stored in the properties of the entity. There isn’t an AttachAsModified method on DbSet, although there are plenty of people asking for one, so don’t be surprised if it turns up in the future. For the moment, you need to set the State property to Modified. Add the UpdateDestination and TestUpdateDestination methods shown in Example 4-6.

Example 4-6. Attaching an existing entity as modified
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 queries for the Grand Canyon Destination from the server, modifies the TravelWarnings property, and passes the updated Destination to the UpdateDestination method on the server. The UpdateDestination method marks the incoming Destination as Modified and saves the changes to the database. If you update the Main method to call TestModifyDestination and run the application, an update statement is sent to the database:

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

Notice that even though we only updated the TravelWarnings property, Entity Framework is updating every column. TravelWarnings gets updated to the new value we set; all the other columns get “updated” to the same values they had when we retrieved the Destination from the database. This is because Entity Framework doesn’t know which properties were updated. We just specified that the entity was Modified, so Entity Framework is updating all the columns to match the current property values.

Registering an Existing Entity for Deletion

In the previous chapter you saw that DbSet.Remove can be used to delete existing entities. You also learned that calling Remove on an entity in the Added state will cancel the addition and cause the context to stop tracking the entity. Calling Remove on an entity that isn’t tracked by the context will cause an InvalidOperationException to be thrown. The Entity Framework throws this exception because it isn’t clear whether the entity you are trying to remove is an existing entity that should be marked for deletion or a new entity that should just be ignored. For this reason, we can’t use just Remove to mark a disconnected entity as Deleted; we need to Attach it first. Add the DeleteDestination and TestDeleteDestination methods shows in Example 4-7.

Example 4-7. Registering an existing entity for deletion
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 method simulates a client application fetching an existing Destination from the server and then passing it to the DeleteDestination method on the server. The DeleteDestination method uses the Attach method to let the context know that it’s an existing Destination. Then the Remove method is used to register the existing Destination for deletion.

Having to attach the entity and then delete it is a little confusing and it’s not immediately clear what the code is trying to achieve when we look at it. Fortunately we can also set the State property of the entity to Deleted. Because Remove is used in attached scenarios, its behavior is different when used on added, unchanged, or disconnected entities. However, changing the State property is only used for explicitly setting state of an entity, so Entity Framework assumes that setting the state to deleted means you want an existing entity marked for deletion. We can rewrite the DeleteDestination method to use this approach and the intent of the code becomes a lot clearer (Example 4-8).

Example 4-8. Registering an entity for deletion using the State property
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 Framework only needs the key value(s) of an entity to be able to construct a DELETE statement for the entity. Therefore, you can reduce the amount of data that gets sent between the server and client by only sending back the key value of entities that need to be deleted. Add another overload of DeleteDestination that just accepts the key of the Destination to be deleted (Example 4-9).

Example 4-9. Registering an entity for deletion based on its key value
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 code constructs a new Destination instance with only the key property set—that’s DestinationId. This entity with only the key value set is known as a stub entity. The code then sets the State property for this new entity to Deleted, indicating that it is an existing entity to be marked for deletion. Because Entity Framework will only access the DestinationId property when it constructs the DELETE statement, it doesn’t matter that the other properties are not populated.

Note

If your entity contains any concurrency tokens, these properties are also used to construct the DELETE statement. You can still use the stub entity approach, but you will need to set values for the concurrency token properties as well.

Working with Relationships with and Without Foreign Keys

If there is one thing you can do to make your life easier in N-Tier scenarios, it’s to expose foreign key properties for the relationships in your model. Relationships that include a foreign key property are called foreign key associations, and unless you have a very good reason not to expose the foreign key properties you will save yourself a lot of pain by including them.

Note

More detailed information on including foreign keys in your Code First model is available in Chapter 4 of Programming Entity Framework: Code First. For Database First and Model First, see Chapter 19 of Programming Entity Framework, 2e.

Benefiting from foreign key properties

The good news is that if you include foreign key properties in your model you already know everything you need to know to work with relationships. If you mark the entity as added, it will get inserted with the value currently assigned to its foreign key property. If you mark an entity as modified, the foreign key property will get updated along with all the other properties. To see this in action, add the UpdateLodging and TestUpdateLodging methods shown in Example 4-10.

Example 4-10. Changing a foreign key value
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)
{
  using (var context = new BreakAwayContext())
  {
    context.Entry(lodging).State = EntityState.Modified;
    context.SaveChanges();
  }
}

The TestUpdateLodging method simulates a client application that fetches a Lodging and changes its DestinationId property. Remember that DestinationId is the foreign key to the Destination that the Lodging belongs to. This time it looks like Dave’s reputation has become national, so he is moving his business around the world from Hawaii, USA, to Queensland, Australia. Once the changes are made, the Lodging is passed to the UpdateLodging method on the server. This method looks very much like the UpdateDestination method you wrote back in Example 4-6. As you can see, there is nothing special required to deal with foreign key relationships.

Using navigation properties to define relationships

You aren’t restricted to using the foreign key property to change relationships. You can still use the navigation properties, and you’ll see this in action a little later in this chapter.

If you chose not to include a foreign key property, you are using independent associations. They are called independent associations because Entity Framework reasons about the relationship independently of the entities that the relationship belongs to, and this makes things difficult when it comes to disconnected entities. In fact, foreign keys are so vital in N-Tier scenarios that the Entity Framework team chose not to expose the methods for changing the state of independent relationships on the DbContext API. To work with them, you will need to drop down to the ObjectContext API.

Note

Delving into the complexities of independent associations in N-Tier scenarios is well beyond the scope of this book. You can find a detailed look at this topic in Chapter 19 of Programming Entity Framework, 2e.

The problem you’ll encounter is that change tracking only tracks scalar properties. When you change a foreign key property, such as Lodging.DestinationId, the context is aware of that property. But when you change a navigation property, there’s nothing to track. Even if you mark the Lodging as Modified, the context is only aware of the scalar properties. When you use an independent association, the context actually keeps track of the relationship itself. It has an object that contains the keys of the two related instances and this is what the context uses to track relationship modifications and update them in the database and the state of the relationship. When your entity is not connected to the context, the context is unable to do the work of modifying these relationship objects. When you reconnect the entities to a context, you need to manually dig down into the context, find those relationship objects, and modify them. It’s pretty complex and very confusing. This is one of the reasons developers convinced the Entity Framework team that we really needed to have foreign key properties available to us after struggling with independent associations in Entity Framework 1.0.

So, given that you simply might find yourself in this same situation, let’s take a look at a quick example to give you an idea of the difference in resolving this problem in a disconnected scenario to the simplicity of working with foreign key associations, and hopefully convince you to stay away from them. Let’s assume for a moment that we hadn’t included the DestinationId property on Lodging and used an independent association instead. The code for UpdateLodging would need to look something like Example 4-11.

Example 4-11. UpdateLodging for independent 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 first thing you’ll notice is that we now require the Destination instance that the Lodging used to belong to. This is because changing an independent association requires that the context have an added relationship to the new entity and a deleted relationship to the previous entity. This is a complicated side effect of the way that Entity Framework handles concurrency checks when updating independent associations. The code starts by marking the lodging as Modified, to take care of updating any properties that aren’t involved in the relationship. The current Destination is also marked as an existing entity. The code then checks to see if this call is changing the Destination this Lodging is assigned to, by comparing the current Destination and the previous Destination. If the Destination does need to be changed, the previous Destination is also marked as an existing entity. The code then uses ObjectContext.ObjectStateManager.ChangeRelationshipState to mark the relationship to the current Destination as Added and the previous Destination as Deleted. With all that taken care of, it’s time to call SaveChanges and push the changes to the database.

Note

Many-to-many relationships are always independent associations. If you have many-to-many relationships in your model, you will need to use ChangeRelationshipState to mark references as Added, Unchanged, or Deleted when processing changes on the server.

Setting the State for Multiple Entities in an Entity Graph

Now that you know the fundamental building blocks, it’s time to plug them together to determine and set the state of each entity in a graph. When a disconnected entity graph arrives on the server side, the server will not know the state of the entities. You need to provide a way for the state to be discovered so that the context can be made aware of each entity’s state. This section will demonstrate how you can coerce the context to infer and then apply entity state.

The first step is to get the graph into the context. You do that by performing an operation that will cause the context to start tracking the root of the graph. Once that is done, you can set the state for each entity in the graph.

Getting the Graph into the Context

Back in Example 4-1 you saw that adding the root of a graph will cause every entity in the graph to be registered with the context as a new entity. This behavior is the same if you use DbSet.Add or change the State property for an entity to Added. Once all the entities are tracked by the state manager, you can then work your way around the graph, specifying the correct state for each entity. It is possible to start by calling an operation that will register the root as an existing entity. This includes DbSet.Attach or setting the State property to Unchanged, Modified, or Deleted. However, this approach isn’t recommended because you run the risk of exceptions due to duplicate key values if you have added entities in your graph. If you register the root as an existing entity, every entity in the graph will get registered as an existing entity. Because existing entities should all have unique primary keys, Entity Framework will ensure that you don’t register two existing entities of the same type with the same key. If you have a new entity instance that will have its primary key value generated by the database, you probably won’t bother assigning a value to the primary key; you’ll just leave it set to the default value. This means if your graph contains multiple new entities of the same type, they will have the same key value. If you attach the root, Entity Framework will attempt to mark every entity as Unchanged, which will fail because you would have two existing entities with the same key.

For example, assume you have an existing Destination that includes two new instances of the Lodging class in its Lodgings property. The key of Lodging is the integer property LodgingId, which is generated by the database when you save. This means the two new Lodgings both have zero assigned to their LodgingId property. If you attempt to register the root as an existing entity, the two Lodging instances will also be registered as existing entities. This will fail because that would mean there are two existing Lodgings with a key of zero.

There may be some cases where you have multiple graphs and/or individual entities that need to be registered with the context. This occurs when not all the entities you need to reason about are reachable from one root. For example, we are going to be writing a method that will save a Destination and the Lodgings that it references. Each of these entities will either be a new entity to be added or an existing entity to be updated. The method will also accept a separate list of Lodgings that should be deleted. Because these Lodgings are to be deleted, the client will probably have removed them from the Lodgings collection on the root Destination. Therefore registering the root Destination won’t be enough to register the deleted Lodgings; we’ll need to register them separately.

Table 4-1 summarizes the options along with the pros and cons that you’ve read in this section.

Table 4-1. Patterns and warnings for joining graphs to a context

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

Building a Generic Approach to Track State Locally

The SaveDestination method we implemented in Example 4-12 isn’t overly complex, but if we expose methods to save various parts of our model, we would be repeating the state setting code over and over again in each method. So let’s take a look at a more generalized approach to applying changes on the server.

Note

You may recognize this pattern from Programming Entity Framework, 2e. It was introduced in Chapter 18 when demonstrating the user of POCOs in WCF Services.

This approach relies on having a consistent way to determine the state of any entity in your model. The easiest way to achieve that is to have an interface or abstract base class that every entity in your model will implement.

For this example we are going to use an IObjectWithState interface that will tell us the current state of the entity. It will have no dependencies on Entity Framework and will be implemented by your domain classes, so it can go in the Model project. Go ahead and add the IObjectWithState interface to the Model project (Example 4-14). Later in this section you’ll add this interface to some of your classes.

Example 4-14. Sample interface to determine state of an entity
namespace Model
{
  public interface IObjectWithState
  {
    State State { get; set; }
  }

  public enum State
  {
    Added,
    Unchanged,
    Modified,
    Deleted
  }
}

Note that we’ve opted for a new enum to represent state rather than reusing the EntityState enum from Entity Framework. This ensures our domain model doesn’t have any dependencies on types from our data access technology.

Before we get to applying state, it would be useful if any entities we retrieve from the database would have their state automatically set to Unchanged. Otherwise the server needs to manually do this before returning the objects to the client. The easiest way to do this is to listen to the ObjectMaterialized event on the underlying ObjectContext. Add the constructor in Example 4-15 to the BreakAwayContext class. You’ll need to add a using statement for the System.Data.Entity.Infrastructure namespace to get access to the IObjectContextAdapter interface.

Example 4-15. Hooking up an event to mark existing entities as Unchanged
public BreakAwayContext()
{
  ((IObjectContextAdapter)this).ObjectContext
    .ObjectMaterialized += (sender, args) =>
      {
        var entity = args.Entity as IObjectWithState;
        if (entity != null)
        {
          entity.State = State.Unchanged;
        }
      };
}

The code uses IObjectContextAdapter to get access to the underlying ObjectContext. It then wires up a new handler to the ObjectMaterialized event, which will fire whenever an entity is returned from a query to the database. Because all objects that come from the database are existing objects, we take this opportunity to mark them as Unchanged if they implement our state tracking interface.

In a real-world scenario you would need to implement the change tracking interface on every class in your model. But for the sake of simplicity, we will just use Destination and Lodging for this demonstration. Go ahead and edit the Lodging and Destination classes to implement the IObjectWithState interface:

public class Destination : IObjectWithState

public class Lodging : IObjectWithState

You’ll also need to add a State property into both of these classes to satisfy the IObjectWithState interface that you just added:

public State State { get; set; }

Now it’s time to write a method that uses all this information to take a disconnected graph and apply the client-side changes to a context by setting the correct state for each entity in the graph.

Warning

One important thing to remember is that this approach is dependent on the client application honoring the contract of setting the correct state. If the client doesn’t set the correct state, the save process will not behave correctly.

Add the SaveDestinationGraph and ConvertState methods shown in Example 4-16.

Example 4-16. Setting state based on a state tracking interface
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();
  }
}

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 code uses DbSet.Add on the root Destination to get the contents of the graph into the context in the Added state. Next it uses the ChangeTracker.Entries<TEntity> method to find the entries for all entities that are tracked by the context and implement IObjectWithState.

Note

The Entries method will give you access to the same object that you would get by calling DbContext.Entry on each entity. There is a nongeneric overload of Entries that will give you an entry for every entity that is tracked by the context. The generic overload, which we are using, will filter the entries to those that are of the specified type, derived from the specified type, or implement the specified interface. If you use the generic overload, the Entity property of each entry object will be strongly typed as the type you specified. You’ll learn more about the Entries method in Chapter 5.

For each entry, the code converts the state from the entities State property to Entity Framework’s EntityState and sets it to the State property for the change tracker entry. Once all the states have been set, it’s time to use SaveChanges to push the changes to the database. Now that we have our generalized solution, let’s write some code to test it out. We’re going to apply the same changes we did back in Example 4-13, but this time using our new method of applying changes. Add the TestSaveDestinationGraph method shown in Example 4-17.

Example 4-17. Testing the new SaveDestinationTest method
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!";
  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 code simulates a client application that queries for an existing Destination and its related Lodgings. The Destination is updated and marked as Modified. The first Lodging is also updated and marked as Modified. The second Lodging is marked for deletion by setting its State property to Deleted. Finally, a new Lodging is put into the Lodgings collection with its State set to Added. The graph is then passed to the SaveDestinationGraph method. If you update the Main method to call TestSaveDestinationGraph and run your application, the same SQL statements from Figure 4-3 will be run against the database.

Creating a Generic Method That Can Apply State Through Any Graph

With some simple tweaks to the SaveDestinationGraph method we wrote in Example 4-16, we can create a method that can work on any root in our model, not just Destinations. Add the ApplyChanges method shown in Example 4-18.

Warning

The generic method demonstrated in this section is specifically designed for use with disconnected scenarios where you have a short-lived context. Notice that the context is instantiated in the ApplyChanges method.

Example 4-18. Generalized method for replaying changes from a disconnected graph of entities
private static void ApplyChanges<TEntity>(TEntity root)
  where TEntity : class, IObjectWithState
{
  using (var context = new BreakAwayContext())
  {
    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 method accepts any root that implements IObjectWithState. Because we don’t know the type of the root until runtime, we don’t know which DbSet to add it to. Fortunately there is a Set<T> method on DbContext that can be used to create a set of a type that will be resolved at runtime. We use that to get a set and then add the root. Next we set the state for each entity in the graph and then push the changes to the database. If you want to test this new method out, change the last line of your TestSaveDestinationGraph method to call ApplyChanges rather than SaveDestinationGraph:

ApplyChanges(canyon);

Running the application will result in the same SQL statements from Figure 4-3 being run again.

There is one potential issue with our ApplyChanges method—at the moment it blindly assumes that every entity in the graph implements IObjectWithState. If an entity that doesn’t implement the interface is present, it will just be left in the Added state and Entity Framework will attempt to insert it. Update the ApplyChanges method as shown in Example 4-19.

Example 4-19. Checking for entities that don’t implement IObjectWithState
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();
  }
}

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 method now calls a CheckForEntitiesWithoutStateInterface helper method that uses the nongeneric overload of ChangeTracker.Entries to get all entities that have been added to the context. It uses a LINQ query to find any of these that don’t implement IObjectWithState. If any entities that don’t implement IObjectWithState are found, an exception is thrown.

Concurrency Implications

This approach works well with timestamp-style concurrency tokens, where the property that is used as a concurrency token will not be updated on the client. For existing entities, the value of the timestamp property will be sent to the client and then back to the server. The entity will then be registered as Unchanged, Modified, or Deleted with the same value in the concurrency token property as when it was originally queried from the database.

If you need to have concurrency checking in your N-Tier application, timestamp properties are arguably the easiest way to implement this. More information on timestamp properties in Code First models is available in Chapter 3 of Programming Entity Framework: Code First. For Database First and Model First, see Chapter 23 of Programming Entity Framework, 2e.

If a property that can be updated on the client is used as a concurrency token, this approach will not suffice. Because this approach does not track the original value of properties, the concurrency token will only have the updated value for the concurrency check. This value will be checked against the database value during save, and a concurrency exception will be thrown because it will not match the value in the database. To overcome this limitation you will need to use the approach described in Recording Original Values.

Tracking Individually Modified Properties

So far, the methods you’ve seen have focused on changing the state of an entity. For a lot of applications, it’s enough to simply track the state at the entity level and update entire objects just as you would when relying on stored procedures. However, you may find yourself wanting to be more granular with the way modified properties are tracked. Rather than marking the entire entity as modified, you might want only the properties that have actually changed to be marked as modified. This is how change tracking works when properties of a tracked entity are modified. It ensures that only the properties that have actually been changed would be included in the update statement. In this section we’ll take a quick look at some common approaches to achieving granular property tracking when the entities are disconnected from the context:

  • Keeping track of modified properties on the client side and passing that list to the server along with the entities

  • Storing the original properties into the entities when they are retrieved from the database before passing them onto the client

  • Requerying the database when the entities have been returned to the server from the client

Note

The samples provided in this section will provide you with a closer look at the Change Tracker API that is tied to the DbContext API. The Entity Framework team worked hard to make our lives easier when dealing with disconnected scenarios. You’ve already seen some of the benefits we developers can reap from their work, and you’ll see more as you read through to the end of this chapter.

Recording Modified Property Names

This first approach is very similar to tracking state at the entity level. In addition to marking an entity as modified, the client is also responsible for recording which properties have been modified. One way to do this would be to add a list of modified property names to the state tracking interface. Update the IObjectWithState interface to include a ModifiedProperties property, as shown in Example 4-20.

Example 4-20. State tracking interface updated to include modified properties
using System.Collections.Generic;

namespace Model
{
  public interface IObjectWithState
  {
    State State { get; set; }
    List<string> ModifiedProperties { get; set; }
  }

  public enum State
  {
    Added,
    Unchanged,
    Modified,
    Deleted
  }
}

You’ll also need to add this property to the Destination and Lodging classes to satisfy this new addition to the IObjectWithState interface:

public List<string> ModifiedProperties { get; set; }

Now that we have a place to record the modified properties, let’s update the ApplyChanges method to make use of it, as shown in Example 4-21.

Example 4-21. Updating ApplyChanges to use ModifiedProperties
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 method are inside the foreach loop where we apply the state. If we find a modified entity in the graph, we mark it as Unchanged, rather than Modified. Once the entity is marked as Unchanged, we loop through the modified properties and mark each of them as modified. We do this using the Property method on the entry to get the change tracking information for the given property and then setting the IsModified property to true. As soon as we mark one of the properties as modified, the state of the entity will also move to Modified. But only the properties we marked as modified will be updated when we save.

Note

There are two overloads of Property, one that accepts the name of the property as a string and the other that accepts a lambda expression representing the property. We are using the string property because we don’t know the names of the properties we want to access until runtime. If you know the name of the property you want to access, you can use the lambda version so that you get compile-time checking of the supplied value (for example, context.Entry(destination).Property(d => d.Name).IsModified = true).

You’ll also need to update the TestSaveDestinationGraph method to populate ModifiedProperties for the Destination and Lodging that are modified:

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

If you run the application again, you will see a set of SQL statements similar to the ones in Figure 4-3. This time, however, the update statements only set the properties we marked as modified. Here is the SQL from the update statement for 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

Concurrency implications

This approach has the same implications for concurrency as the generic approach you saw in the previous section. Timestamp concurrency properties will work well, but concurrency properties that can be modified on the client will cause issues.

Recording Original Values

An alternative to asking the client to record the properties that were modified is to keep track of the original values for existing entities. One of the big advantages of this approach is that you are no longer relying on the client to tell you which properties were modified. This makes the code on the client side much simpler and less error prone. Entity Framework can then check for changes between the original and current values to determine if anything is modified. Let’s change the IObjectWithState interface to record original values rather than modified properties. Because we are going to calculate whether an entity is modified or not, we no longer need the Modified option in the State enum, so let’s remove that, too. These changes are shown in Example 4-22.

Example 4-22. Change state tracking interface to use original values
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 need to remove the ModifiedProperties property from Destination and Lodging and add in the new OriginalValues property:

public Dictionary<string, object> OriginalValues { get; set; }

We want Entity Framework to automatically populate the OriginalValues property when an entity is retrieved from the database, so let’s update the event handler we added to the constructor of our context (Example 4-23). You’ll need to add a using statement for the System.Collections.Generic namespace.

Example 4-23. Populating original values after query
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 addition to marking the entity as Unchanged, this updated code will populate the OriginalValues property. It does this by getting the original values from the change tracking entry for the entity and using the BuildOriginalValue helper method to convert them to the required dictionary format. The helper method loops through each of the properties that we have original values for. If the value is just a normal scalar property, it copies the value of the property to into the resulting dictionary. If the value is a DbPropertyValues, this indicates that it is a complex property and the code uses a recursive call to build a nested dictionary of the values in the complex property. More information on nested DbPropertyValues for complex properties is available in Working with Complex Properties.

Because the entity has just been returned from the database, the current and original values are the same, so we could have also used the CurrentValues property to get the values. Now we can update the ApplyChanges method to make use of this property (Example 4-24).

Example 4-24. Using original values in ApplyChanges
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;
    }
  }
}

After painting the state of entities throughout the graph, the code now checks to see if it’s an existing entity. For existing entities the code uses the ApplyPropertyChanges helper method to set the original values for the entity. The helper method loops through the OriginalValues that were captured when the entity was retrieved from the database. If the value is a nested dictionary, indicating a complex property, then it uses a recursive call to apply the changes for the individual properties inside the complex property. If the value is just a scalar value, it updates the original value being stored by the context. Entity Framework will detect if any of the values differ from the values currently assigned to the properties of the entity. If a difference is detected, the property, and therefore the entity, will be marked as modified. We also need to update the ConvertState method because we no longer have a Modified option in the State enum (Example 4-25).

Example 4-25. ConvertState updated to reflect removal of Modified state
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 update the TestSaveDestinationGraph method to no longer mark entities as Modified when it changes properties (Example 4-26). This is no longer required because the ApplyChanges method will calculate this for you.

Example 4-26. Updating the test method to test recording of original values
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();
  secondLodging.State = State.Deleted;

  canyon.Lodgings.Add(new Lodging
  {
    Name = "Big Canyon Lodge",
    State = State.Added
  });

  ApplyChanges(canyon);
}

If you run the application, you will get the familiar set of SQL statements from Figure 4-3. The update statements that are generated will only set properties that were actually modified.

Concurrency implications

This approach offers the best concurrency support because it records the same information that is stored by the change tracker when modifying entities that are attached to a context. Timestamp concurrency properties will work because the value retrieved from the database is sent to the client and then back to the server, to be used when updating existing data. Concurrency properties that can be modified will also work because the original value, which was assigned to the property when it was retrieved from the database, is recorded. Because this value is set as the original value for the property, Entity Framework will use the original value when performing concurrency checks.

Querying and Applying Changes

Another approach that developers sometimes try is to calculate the modified properties by querying the database to get the current entity and then copying the values from the incoming entity. Because this approach requires one query to get the entity from the database and often a second query to update the data, it’s usually slower than just marking the entity as modified and updating every column. That said, sending a lot of unnecessary updates to the database isn’t ideal. If one of the other techniques in this chapter doesn’t work for you, this may be worth looking at.

Entity Framework makes it easy to copy the values from one object to another. Putting graphs aside for a moment, we could implement an UpdateDestination method as follows:

public static void UpdateDestination(Destination destination)
{
  using (var context = new BreakAwayContext())
  {
    if (destination.DestinationId > 0)
    {
      var existingDestiantion = context.Destinations
        .Find(destination.DestinationId);

      context.Entry(existingDestiantion)
        .CurrentValues
        .SetValues(destination);
    }
    else
    {
      context.Destinations.Add(destination);
    }

    context.SaveChanges();
  }
}

If the Destination has a key value assigned, it’s assumed to be an existing Destination. The Find method is used to load the Destination from the database. The SetValues method is used on CurrentValues to copy values from the incoming Destination to the existing Destination from the database. Entity Framework will automatically detect if any of the property values are different. If there are differences, the appropriate properties will be marked as modified.

This approach falls down when you start working with graphs, though. Let’s assume the incoming Destination references a new Lodging. We can’t query for this Lodging from the database, since it’s new, so we need to register this Lodging for addition. The problem is if we try and add the Lodging to the context, it will also try and add any other entities that it references. If the Lodging references an existing entity, it’s going to end up in the context in the added state. This gets very complicated, because we now want to try and take this existing entity back out of the context so that we can query for the entity from the database and copy its values over.

While it is technically possible to make this work, the code gets very complicated. Fortunately, there is a better alternative. Rather than getting the existing entity from the database and copying the values to it, we can attach the incoming entity and then set its original values to the values from the database. Update the ApplyChanges method as shown in Example 4-27.

Example 4-27. ApplyChanges checks for modification using database values
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)
      {
        var databaseValues = entry.GetDatabaseValues();
        entry.OriginalValues.SetValues(databaseValues);
      }
    }

    context.SaveChanges();
  }
}

Rather than getting the original values that were recorded when the entity was retrieved from the database, the code now gets the current original values from the database. In fact, the OriginalValues property on IObjectWithState is now no longer required. The database values are retrieved using the GetDatabaseValues method. This method returns DbPropertyValues, which is the same type returned from the CurrentValues and OriginalValues property on an entity. The SetValues method on DbPropertyValues will copy the values from any other DbPropertyValues instance into itself. We use this functionality to copy the database values into the original values for each entity. If you run the application, SQL statements similar to those from Figure 4-3 will be executed against the database. However, this time the update statements will only set the properties that were actually changed.

Concurrency implications

This approach bypasses concurrency checks because it requeries for the database values just before saving any changes. These new database values are used in place of the values that were originally retrieved from the database when sending data to the client. If you are using concurrency tokens, this approach to replaying changes on the server is not suitable.

Get Programming Entity Framework: DbContext now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.