O'Reilly logo

Programming Entity Framework: DbContext by Rowan Miller, Julia Lerman

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

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.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required