Chapter 3. Adding, Changing, and Deleting Entities

In the previous chapter you saw how to get data from the database into memory. But this is only half the story. Most applications also need to make changes to that data and then push those changes back into the database. In this chapter we will take a look at how Entity Framework can be used to make changes to data. These changes fall into three main categories: adding new data, changing existing data and deleting existing data.

While looking at querying, we saw the main benefit of using an Object Relational Mapper (ORM), like Entity Framework, is that application code is written in terms of your object model. As you write your application, you don’t need to be looking at the shape of your tables and columns. Nor do you need to know how to write INSERT, UPDATE, and DELETE statements for your database. Entity Framework will take care of translating the operations you perform on your objects into SQL statements that will push these changes into the database.

As you perform operations on your object instances, Entity Framework uses its change tracker to keep track of what you have done. When you’re ready to commit the changes to the database, you call the SaveChanges method. SaveChanges will invoke the update pipeline, which is responsible for translating the changes to your object instances into SQL statements that are executed against your database. If you’ve developed applications using Entity Framework’s ObjectContext, you should be familiar with this process.

Because Entity Framework is aware of the relationships between your entities, if you are saving related objects, it will take care of ordering the SQL statements to ensure changes are applied in the correct order. For example, you may be deleting an existing Destination and also moving the Lodgings associated with that Destination to a different Destination. Entity Framework will determine that the Lodging records must be updated before the Destination record is deleted, regardless of the order that you performed these operations in memory.

Entity Framework allows you to make changes that affect single objects, a relationship between two objects, or an entire graph of objects. In this chapter we are going to take a look at changes affecting a single object and relationships between objects. In the next chapter we’ll take a look at some advanced scenarios where operations can affect a whole graph of objects.

Working with Single Entities

There are three types of changes that can affect a single entity—adding a new entity, changing the property values of an existing entity, or deleting an existing entity. In this section you’ll learn how to make each of these changes using Entity Framework.

Adding New Entities

Adding a new object with Entity Framework is as simple as constructing a new instance of your object and registering it using the Add method on DbSet. Let’s say you wanted to add a Machu Picchu destination to the database. The AddMachuPicchu method shown in Example 3-1 demonstrates this.

Example 3-1. Adding a new Destination
private static void AddMachuPicchu()
{
  using (var context = new BreakAwayContext())
  {
    var machuPicchu = new Destination
    {
      Name = "Machu Picchu",
      Country = "Peru"
    };

    context.Destinations.Add(machuPicchu);
    context.SaveChanges();
  }
}

The code constructs a new Destination for Machu Picchu and then calls Add on the Destinations set you defined in the BreakAwayContext. Finally, the code calls SaveChanges, which will take the changes and save them to the database. We see that a single INSERT statement is executed against our database:

exec sp_executesql N'
insert [baga].[Locations]
    ([LocationName], [Country], [Description], [Photo])
    values (@0, @1, null, null)

select [LocationID]
    from [baga].[Locations]
    where @@ROWCOUNT > 0 and [LocationID] = scope_identity()',

N'@0 nvarchar(max) ,@1 nvarchar(max) ',
@0=N'Machu Picchu',@1=N'Peru'

Notice that Entity Framework is using the mapping we supplied as it translates the object changes into SQL. For example, we mapped the Destination class in our domain model to the baga.Locations table in the database. Entity Framework uses this information to construct an INSERT statement that targets the Locations table. The key of Destination is an identity column, meaning the value is generated by the database when the record is inserted. Because of this, Entity Framework includes some additional SQL to fetch this newly created value after the INSERT statement has executed. Entity Framework will then take the returned value and assign it to the DestinationId property of the object that was added.

Note

In this example we used the default constructor of our POCO class to create the new instance to be inserted. A little later in this chapter you’ll learn about change tracking proxies and how to create new instances of proxies to insert.

Changing Existing Entities

Changing existing objects is as simple as updating the value assigned to the property(s) you want changed and calling SaveChanges. Perhaps we want to change the Grand Canyon Destination and assign a Description to it so that BAGA customers know just how grand it is. Add the ChangeGrandCanyon method shown in Example 3-2.

Example 3-2. Changing an existing Destination
private static void ChangeGrandCanyon()
{
  using (var context = new BreakAwayContext())
  {
    var canyon = (from d in context.Destinations
                       where d.Name == "Grand Canyon"
                       select d).Single();

    canyon.Description = "227 mile long canyon.";

    context.SaveChanges();
  }
}

This code uses a LINQ query to load the Grand Canyon Destination into memory. It then assigns the new value to the Description property of the loaded Destination. With the changes completed it calls SaveChanges, which issues an UPDATE statement to the database:

exec sp_executesql N'
update [baga].[Locations]
    set [Description] = @0
    where ([LocationID] = @1)
',N'@0 nvarchar(500),@1 int',
@0=N'227 mile long canyon.',@1=1

Again we see Entity Framework using the mapping to construct the appropriate SQL statement. This time it’s an UPDATE statement against the baga.Locations table. Entity Framework uses the key of the entity to identify the record to be updated. In our case, that’s DestinationId, which is mapped to the LocationId column. This results in a WHERE clause that filters based on the LocationId column with the value from the DestinationId property in the object being updated.

Deleting Existing Entities

To delete an entity using Entity Framework, you use the Remove method on DbSet. Remove works for both existing and newly added entities. Calling Remove on an entity that has been added but not yet saved to the database will cancel the addition of the entity. The entity is removed from the change tracker and is no longer tracked by the DbContext. Calling Remove on an existing entity that is being change-tracked will register the entity for deletion the next time SaveChanges is called.

Note

You may be wondering why the Entity Framework team chose to call the method Remove rather than Delete, and for that matter, why they chose Add instead of Insert. The names were chosen for consistency with other collections and sets in the .NET Framework. Other collections all use the Add/Remove pair of methods to bring elements into and out of the collection.

Let’s add a DeleteWineGlassBay method that will delete the Wine Glass Bay Destination from our database (Example 3-3).

Example 3-3. Deleting an existing Destination
private static void DeleteWineGlassBay()
{
  using (var context = new BreakAwayContext())
  {
    var bay = (from d in context.Destinations
               where d.Name == "Wine Glass Bay"
               select d).Single();

    context.Destinations.Remove(bay);
    context.SaveChanges();
  }
}

The code uses a LINQ query to load the Wine Glass Bay Destination from the database. It then calls Remove on the Destinations set you have defined on the BreakAwayContext. Now that Wine Glass Bay is registered for deletion (at least from our database), we call SaveChanges and a DELETE statement is run against our database:

exec sp_executesql N'
delete [baga].[Locations]
    where ([LocationID] = @0)',
N'@0 int',
@0=3

This is a very simple DELETE statement that uses the key value from the Wine Glass Bay object to build a WHERE clause that identifies the object we are deleting.

Deleting without loading from the database

DbSet.Remove follows the same rule that we’ve always had to contend with for having Entity Framework deleting objects. The object must be tracked by the change tracker and marked as Deleted in order for SaveChanges to construct a DELETE command to send to the database.

If you know you need to delete an entity, but it’s not already in memory, it’s a little inefficient to retrieve that entity from the database just to delete it. If you know the key of the entity you want to delete, you can attach a stub that represents the entity to be deleted, and then delete this stub. A stub is an instance of an entity that just has the key value assigned. The key value is all that’s required for deleting entities.

When attaching a stub you use the DbSet.Attach method to let Entity Framework know that it’s an existing entity. Once an entity is attached, it behaves just like an entity that was retrieved from the database. So calling DbSet.Remove will cause a DELETE statement to be sent to the database during SaveChanges. For example, the following code would delete the Destination with an ID of 2, without loading it from the database:

var toDelete = new Destination { DestinationId = 2 };
context.Destinations.Attach(toDelete);
context.Destinations.Remove(toDelete);
context.SaveChanges();

If an entity has been loaded into memory, you won’t be able to attach a stub for the entity. Doing so would cause two existing entities with the same key value to be tracked by the context. If you try and do this you will get an InvalidOperationException stating “An object with the same key already exists in the ObjectStateManager.”

Another way to delete entities without loading them is to use DbContext.Database.ExecuteSqlCommand to execute some raw SQL to perform the deletion in the database. For example, the following code would delete the Hawaii Destination without loading it from the database:

context.Database.ExecuteSqlCommand(
  "DELETE FROM baga.Locations WHERE LocationName = 'Hawaii'");

Because we are using raw SQL, we are bypassing any mapping that is done using Entity Framework. In the above code we needed to remember that the Destination class is mapped to the baga.Locations table and that the Name property is mapped to the LocationName column.

If you are deleting objects that have related data, you may need to update related data for the delete to succeed. The required updates to related data will depend on whether the relationship is optional or required. Optional relationships mean that the child entity can exist in the database without a parent assigned. For example, a Reservation can exist in the database without a Trip assigned. Required relationships mean the child entity cannot exist without a parent assigned. For example, a Lodging cannot exist in the database without being assigned to a Destination.

If you delete an entity that is the parent of an optional relationship, the relationship between the parent and any child entities can be deleted, too. This means the child entities will be updated so that they are no longer assigned to a parent—the foreign key column in the database will be set to null. Entity Framework will automatically delete the relationship for you if the child entity has been loaded into memory from the database.

Let’s start by seeing what happens if we delete a parent entity of an optional relationship when the child entity isn’t loaded into memory. There is an optional relationship between a Reservation and a Trip: Trip is the parent and Reservation is the child. Add a method that tries to delete a Trip without its child Reservation loaded into memory (Example 3-4).

Example 3-4. Deleting a Trip without its child Reservation loaded
private static void DeleteTrip()
{
  using (var context = new BreakAwayContext())
  {
    var trip = (from t in context.Trips
                where t.Description == "Trip from the database"
                select t).Single();

    context.Trips.Remove(trip);
    context.SaveChanges();
  }
}

If you update the Main method to call DeleteTrip and run the application, you will get a DbUpdateException informing you that there was an error while saving. If you drill into the InnerException properties, you will see that the innermost exception is a SqlException stating that “The DELETE statement conflicted with the REFERENCE constraint ‘FK_Reservations_Trips_Trip_Identifier’.” You get this exception because there is still a Reservation in the database that has a foreign key pointing to the Trip you are trying to delete. Let’s update the DeleteTrip method so that the Reservation that references the Trip we are deleting is loaded into memory (Example 3-5).

Example 3-5. Deleting a Trip with its child Reservation loaded
private static void DeleteTrip()
{
  using (var context = new BreakAwayContext())
  {
    var trip = (from t in context.Trips
                where t.Description == "Trip from the database"
                select t).Single();

    var res = (from r in context.Reservations
                where r.Trip.Description == "Trip from the database"
                select r).Single();

    context.Trips.Remove(trip);
    context.SaveChanges();
  }
}

The updated code uses a second LINQ query to load the single Reservation that is assigned to the Trip that is being deleted. If you run the application again, it will succeed and two SQL statements are sent to the database (Figure 3-1).

Deleting parent entity and optional relationship to child entity
Figure 3-1. Deleting parent entity and optional relationship to child entity

The update statement sets the foreign key column of the Reservation to null, so that it is no longer related to the Trip that is being deleted. The delete statement then deletes the Trip from the database.

Required relationships are a bit different because the foreign key column in the database can’t be set to null. If you delete a parent entity, each child must either be deleted or updated to belong to a different parent entity. You can either do this manually or have the child entities automatically deleted, using a cascade delete. Failure to delete or reassign child records when you delete a parent will result in a referential integrity constraint violation when you attempt to SaveChanges.

In our model there is a cascade delete defined between Lodging and Destination. If we delete a Destination, the Lodging instances that are assigned to it will get automatically deleted. In our database there is a Grand Canyon Destination, which has two related Lodgings. Add the DeleteGrandCanyon method shown in Example 3-6.

Example 3-6. Deleting the Grand Canyon with related Lodgings
private static void DeleteGrandCanyon()
{
  using (var context = new BreakAwayContext())
  {
    var canyon = (from d in context.Destinations
                  where d.Name == "Grand Canyon"
                  select d).Single();

    context.Entry(canyon)
      .Collection(d => d.Lodgings)
      .Load();

    context.Destinations.Remove(canyon);
    context.SaveChanges();
  }
}

The code loads the Grand Canyon Destination from the database and then uses eager loading to ensure the related Lodgings are also loaded into memory. The code then marks the Grand Canyon for deletion and pushes the changes to the database. If you update the Main method to call DeleteGrandCanyon and run the application, three SQL commands get sent to the database (Figure 3-2).

Deleting the parent of a required relationship with cascade delete
Figure 3-2. Deleting the parent of a required relationship with cascade delete

Because the related Lodgings were loaded into memory, and we had a cascade delete rule configured, Entity Framework has automatically deleted the related Lodgings. The first two delete statements are deleting the related Lodgings and the final delete statement deletes the Grand Canyon Destination.

Cascade delete is also configured in the database, so we don’t need to load the related data for it to be automatically deleted. Modify the DeleteGrandCanyon method so that it no longer loads the related Lodgings into memory (Example 3-7).

Example 3-7. Deleting Grand Canyon without Lodgings loaded
private static void DeleteGrandCanyon()
{
  using (var context = new BreakAwayContext())
  {
    var canyon = (from d in context.Destinations
                  where d.Name == "Grand Canyon"
                  select d).Single();

    context.Destinations.Remove(canyon);
    context.SaveChanges();
  }
}

If you run the application again, a single delete command is sent to the database, to delete the Grand Canyon Destination. Because there is a cascade delete configured on the foreign key constraint between Lodging and Destination, the database has taken care of deleting the related data. Cascade delete is covered in detail in Programming Entity Framework, 2e.

If we didn’t have a cascade delete defined, we would need to manually mark each of the related Lodging entities as deleted before attempting to save. To mark each of the related Lodgings as deleted, you would need to iterate through the Lodgings property and call DbSet.Remove on each Lodging. Because Entity Framework is going to update the Lodgings property to remove each Lodging as it is marked for deletion, you need to use ToList to create a copy of the Lodgings. Failure to create a copy will result in the contents of Lodgings changing as it is iterated, which is not supported by the .NET Framework:

foreach (var lodging in canyon.Lodgings.ToList())
{
  context.Lodgings.Remove(lodging);
}

An alternative to deleting the child entities is to assign them to a new parent. For example, we could move the Lodgings from Grand Canyon to Hawaii before deleting the Grand Canyon:

foreach (var lodging in canyon.Lodgings.ToList())
{
  lodging.Destination = hawaii;
}

You’ll learn more about changing relationships in Changing a Relationship Between Objects.

Multiple Changes at Once

So far we have looked at making a single change followed by a call to SaveChanges to push that change to the database. Your application may want to intersperse many queries and changes and then push all the changes to the database at once. Let’s add a MakeMultipleChanges method that does just this (Example 3-8).

Example 3-8. Multiple changes in one transaction
private static void MakeMultipleChanges()
{
  using (var context = new BreakAwayContext())
  {
    var niagaraFalls = new Destination
    {
      Name = "Niagara Falls",
      Country = "USA"
    };

    context.Destinations.Add(niagaraFalls);

    var wineGlassBay = (from d in context.Destinations
                  where d.Name == "Wine Glass Bay"
                  select d).Single();

    wineGlassBay.Description = "Picturesque bay with beaches.";

    context.SaveChanges();
  }
}

The code creates a new Destination for Niagara Falls and adds it to the Destinations set. It then retrieves the Wine Glass Bay Destination and changes its description. Once these changes are made, SaveChanges is called to push the changes to the database. If you update the Main method to call MakeMultipleChanges and run the application you will see three statements are run against the database (Figure 3-3).

Multiple changes from one call to SaveChanges
Figure 3-3. Multiple changes from one call to SaveChanges

The first is a SELECT statement to fetch the Wine Glass Bay Destination. The next two statements are an UPDATE and an INSERT that are run when we call SaveChanges.

Note

SaveChanges is transactional, meaning that it either pushes all the changes to the database or none of them. If one change fails, any changes that have already been made are rolled back and the database is left in the state it was in before SaveChanges was called. You can learn more about how Entity Framework uses transactions by default and how to override that default behavior in Chapter 20 of Programming Entity Framework, 2e.

The “Find or Add” Pattern

You may have noticed that DbSet.Add returns an object. It returns the same object that you pass into the method. This may seem a little strange at first, but it enables a nice coding pattern that you may find convenient to use in your applications. Your application might allow a user to search for a Person based on his or her SocialSecurityNumber. If the Person is found, your code can use the existing entity. But if the Person isn’t located, you want to create a new Person with the supplied SocialSecurityNumber. The FindOrAddPerson method shown in Example 3-9 demonstrates this pattern.

Example 3-9. Adding a new Person if existing record doesn’t exist
private static void FindOrAddPerson()
{
  using (var context = new BreakAwayContext())
  {
    var ssn = 123456789;

    var person = context.People.Find(ssn)
      ?? context.People.Add(new Person
      {
        SocialSecurityNumber = ssn,
        FirstName = "<enter first name>",
        LastName = "<enter last name>"
      });

    Console.WriteLine(person.FirstName);
  }
}

Remember that DbSet.Find is an easy way to locate entities based on their key values. If it finds the entity either in memory or in the database, it will return the correct instance. But if it can’t locate the entity, Find will return null. You can combine Find with the ?? operator that allows you to provide an alternate value to return if some code returns null. In the example above, we attempt to locate the entity by using Find based on its key. If Find doesn’t locate the entity, we add a new Person to the context instead.

The ?? operator is specific to C#; however, the same logic can be written in VB.NET using the If method:

Dim person = If(context.People.Find(ssn), context.People.Add(
  New Person() With {
    .SocialSecurityNumber = ssn,
    .FirstName = "<enter first name>",
    .LastName = "<enter last name>"
    }
))

Working with Relationships

Now that you know how to add, change, and delete entities, it’s time to look at how we change relationships between those entities. Your domain model exposes relationships using navigation properties and, optionally, a foreign key property. Changing a relationship is achieved by changing the values assigned to those properties.

Given that a relationship can be represented by up to three properties (two navigation properties and a foreign key property), you may be wondering if you need to update all three just to change the relationship. Updating just one of these properties is enough to let Entity Framework know about the change. It is also fine to update more than one of the properties if you want to, provided that the changes represent the same change. When you call SaveChanges, Entity Framework will take care of updating the rest of these properties for you; this is known as relationship fix-up. Rather than waiting for SaveChanges to fix up the properties, you can trigger this fix-up on demand by calling DetectChanges or have it happen in real-time by using change tracking proxies. Both of these concepts are described later in this chapter.

While the basics of changing relationships are quite simple, there are a lot of intricate details to be familiar with as you get into more advanced relationship scenarios. These intricacies are not specific to the DbContext API and are well beyond the scope of this book.

Note

You can find a detailed look at relationships in Chapter 19 of Programming Entity Framework, 2e.

Adding a Relationship Between Objects

To add a new relationship, you need to assign one of the objects in the relationship to the navigation property of the other object. If the navigation property you want to change is a reference (for example, the Destination property of the Resort class), you set the value to the related object. If the navigation property is a collection (for example, the Payments property of the Reservation class), you use the Add method to add it to that collection. Remember that the change can be made at one or both ends of the relationship.

Let’s assume you want to add a Lodging record for a new luxury resort that is opening, and you want to associate it with the Grand Canyon. To follow along, add the NewGrandCanyonResort method shown in Example 3-10.

Example 3-10. Adding a new relationship
private static void NewGrandCanyonResort()
{
  using (var context = new BreakAwayContext())
  {
    var resort = new Resort
    {
      Name = "Pete's Luxury Resort"
    };

    context.Lodgings.Add(resort);

    var canyon = (from d in context.Destinations
                  where d.Name == "Grand Canyon"
                  select d).Single();

    canyon.Lodgings.Add(resort);

    context.SaveChanges();
  }
}

This code creates the new Resort and adds it to the Lodgings set we defined on BreakAwayContext (remember that Resort derives from Lodging). Next, the code locates the Grand Canyon Destination that we want to add this new Resort to. Then the new Resort is added to the Lodgings collection of the Grand Canyon. This lets Entity Framework know that the two objects are related. SaveChanges is then used to push these changes to the database.

Note

In Example 3-10, we added the new Resort to the Lodgings set and then added it to the canyon.Lodgings collection. The example is intentionally redundant for clarity as you learn about the behaviors. The first call ensures that the context knows that resort is new and needs to be inserted into the database. The second call then specifies that resort must also be related to canyon. While this makes it obvious that resort is a new entity, adding it twice is not strictly necessary in this particular example. If you had skipped adding the new resort to the context and only added it to canyon, Entity Framework would have found resort because it is now referenced from the navigation property of an entity that is tracked by the context (canyon). Entity Framework would have recognized that resort was not being tracked and in response would have assumed the resort needed to be in the Added state. Therefore we could have left out the line of code that added resort to context.Lodgings and achieved the same result.

In the code we updated the collection end of a one-to-many relationship. But remember we can update either end of the relationship. Rather than adding the new Resort to the Destination.Lodgings collection, we could have set the Lodging.Destination property to the desired Destination instance:

resort.Destination = canyon;

Lodging also exposes a foreign key property, DestinationId, to represent the relationship. We could also have updated that instead:

resort.DestinationId = canyon.DestinationId;

If you are adding an object to a collection navigation property, you need to make sure that the collection property will be initialized. Remember that, by default, properties will be assigned a value of null. In our case we enabled lazy loading on the Lodgings property, so Entity Framework took care of creating a collection and assigning it to the Lodgings property. If you aren’t using lazy loading, you will need to include logic in either your classes or the consuming code to initialize the collection.

Changing a Relationship Between Objects

Changing a relationship is actually the same as adding a new relationship. When we add a relationship, we are changing it from “unassigned” to point to an entity. Changing a relationship to point from one entity to another uses exactly the same process. To change a relationship, we locate the entity to be changed and update the navigation property, or foreign key property.

Perhaps we made a mistake while entering some data and the Grand Hotel actually exists at the Great Barrier Reef rather than the Grand Canyon. The ChangeLodgingDestination method shown in Example 3-11 demonstrates assigning a new relationship that will replace an existing relationship.

Example 3-11. Updating an existing relationship
private static void ChangeLodgingDestination()
{
  using (var context = new BreakAwayContext())
  {
    var hotel = (from l in context.Lodgings
                     where l.Name == "Grand Hotel"
                     select l).Single();

    var reef = (from d in context.Destinations
                  where d.Name == "Great Barrier Reef"
                  select d).Single();

    hotel.Destination = reef;

    context.SaveChanges();
  }
}

The code locates both the Grand Hotel and the Great Barrier Reef by using LINQ queries. Next it updates the relationship by changing the Destination property of the Grand Hotel to point to the Great Barrier Reef. There is no need to remove the existing relationship between the Grand Hotel and the Grand Canyon. Entity Framework knows that we want the relationship to be updated, which implies it will no longer point to the old value. Running the code will result in a single update statement being sent to the database:

exec sp_executesql N'update [dbo].[Lodgings]
set [destination_id] = @0
where ([LodgingId] = @1)
',N'@0 int,@1 int',@0=4,@1=1

The update statement looks very similar to the one you saw earlier in this chapter, when we modified a String property of an entity. Entity Framework uses the Locations key value to locate the record to be updated. This time, instead of updating a simple column, it is updating the foreign key column to point to the primary key of the Destination we updated our Location to.

By now you’ve probably worked out that we could also make the change by adding hotel to the Lodgings property of reef:

reef.Lodgings.Add(hotel);

We could also make the change by setting the foreign key property:

hotel.DestinationId = reef.DestinationId;

Removing a Relationship Between Objects

Let’s say that Dave is no longer the primary contact for Dave’s Dump. In fact, the service is so bad at this lodging that they no longer have a contact at all. This means we simply want to remove the relationship rather than changing it to a new Person.

To remove a relationship, you can remove the target object from a collection navigation property. Alternatively, you can set a reference navigation property to null. If your classes expose a nullable foreign key property for the relationship, a third option is to set the foreign key to null.

Warning

Removing relationships by changing the foreign key is only possible with nullable properties (for example, the PrimaryContactId property of the Lodging class). If your foreign key is an int (for example, the DestinationId property of the Lodging class), you won’t be able to set the value to null and by convention, that relationship would be required. Setting the value to 0 would cause a primary key/foreign key constraint error in the database, as there will be no parent whose primary key is equal to 0.

Add the RemovePrimaryContact method shown in Example 3-12.

Example 3-12. Removing a primary contact for Dave’s Dump
private static void RemovePrimaryContact()
{
  using (var context = new BreakAwayContext())
  {
    var davesDump = (from l in context.Lodgings
                     where l.Name == "Dave's Dump"
                     select l).Single();

    context.Entry(davesDump)
      .Reference(l => l.PrimaryContact)
      .Load();

    davesDump.PrimaryContact = null;

    context.SaveChanges();
  }
}

The code starts by fetching Dave’s Dump from the database with a LINQ query. Next it loads the related PrimaryContact. We need to do this so that something is actually changing when we set the value to null. Because lazy loading isn’t enabled for this property it is already null by default so the code explicitly loads the PrimaryContact. Eager loading or enabling lazy loading would achieve exactly the same thing. Once the contact is loaded, the PrimaryContact property is set to null to let Entity Framework know that we want to delete this relationship. Dave will not be deleted from the database, nor will Dave’s Dump, but they will no longer be related to each other. SaveChanges will update the database to null out the foreign key for the primary contact of Dave’s Dump.

This highlights one of the advantages of exposing foreign key properties in your classes. Because we expose a foreign key property for the PrimaryContact relationship, and that property used a nullable integer (Nullable<int> or int?), then we can set that property to null without the need to load the related data. This works because foreign key properties are always populated when you query for an entity, whereas navigation properties are only populated when you load the related data. Let’s rewrite the RemovePrimaryContact method to modify the foreign key property rather than the navigation property (Example 3-13).

Example 3-13. Removing a primary contact for Dave’s Dump using the foreign key
private static void RemovePrimaryContact()
{
  using (var context = new BreakAwayContext())
  {
    var davesDump = (from l in context.Lodgings
                      where l.Name == "Dave's Dump"
                      select l).Single();

    davesDump.PrimaryContactId = null;

    context.SaveChanges();
  }
}

In this example, we were removing an optional relationship; according to the model we built, it is fine for Lodging to exist without a PrimaryContact. If we tried to remove a required relationship the outcome would be a little different. Let’s say we had tried to remove the relationship between Dave’s Dump and the Grand Canyon. According to the BAGA model, Lodging can’t exist without a Destination. Entity Framework would allow us to set the Lodging.Destination property to null, but it would throw an exception when we tried to SaveChanges. We are allowed to set the property to null provided we set it to another valid Destination before we try and SaveChanges.

Given that Dave is no longer a contact, we may want to remove him from the database, rather than just removing his relationship as a primary contact for Dave’s Dump. If you delete an entity there is no need to delete all the relationships that they participate in: Entity Framework will automatically delete them for you. As discussed in Deleting Existing Entities, if any of the relationships are required you will need to delete or reassign any child entities before saving.

Working with Change Tracking

Throughout this chapter you have seen that Entity Framework keeps track of the changes you make to your objects. Entity Framework uses its change tracker to do this. You can access the change tracker information, and some change tracking–related operations, through the DbContext.ChangeTracker property. You’ll see more about the Change Tracker API in Chapter 5.

There are two different ways that Entity Framework can track the changes to your objects: snapshot change tracking or change tracking proxies.

Snapshot change tracking

The code written so far in this chapter has relied on snapshot change tracking. The classes in our model are all POCO and they don’t contain any logic to notify Entity Framework when a property value is changed. Because there is no way to be notified when a property value changes, Entity Framework will take a snapshot of the values in each property when it first sees an object and store the values in memory. This snapshot occurs when the object is returned from a query or when we add it to a DbSet. When Entity Framework needs to know what changes have been made, it will scan each object and compare its current values to the snapshot. This process of scanning each object is triggered through a method of ChangeTracker called DetectChanges.

Change tracking proxies

The other mechanism for tracking changes is through change tracking proxies, which allow Entity Framework to be notified of changes as they are made. In Chapter 2, you learned about dynamic proxies that are created for lazy loading. Change tracking proxies are created using the same mechanism, but in addition to providing for lazy loading, they also have the ability to communicate changes to the context.

To use change tracking proxies, you need to structure your classes in such a way that Entity Framework can create a dynamic type at runtime that derives from our POCO class and override every property. This dynamic type, known as a dynamic proxy, includes logic in the overridden properties to notify Entity Framework when those properties are changed. In fact, all of the rules for creating dynamic change tracking proxies from POCOs that you learned about if you read Programming Entity Framework, 2e, are the same when you are using POCOs with DbContext.

Using Snapshot Change Tracking

Snapshot change tracking depends on Entity Framework being able to detect when changes occur. The default behavior of the DbContext API is to automatically perform this detection as the result of many events on the DbContext. DetectChanges not only updates the context’s state management information so that changes can be persisted to the database, it also performs relationship fix-up when you have a combination of reference navigation properties, collection navigation properties and foreign keys. It’s important to have a clear understanding of how and when changes are detected, what to expect from it and how to control it. This section addresses those concerns.

Understanding When Automatic Change Detection Occurs

The DetectChanges method of ObjectContext has been available since Entity Framework 4 as part of the snapshot change tracking pattern on POCO objects. What’s different about DbContext.ChangeTracker.DetectChanges (which in turn, calls ObjectContext.DetectChanges) is that there are many more events that trigger an automatic call to DetectChanges. Here is the list of the method calls you should already be familiar with that will cause DetectChanges to do its job:

  • DbSet.Add

  • DbSet.Find

  • DbSet.Remove

  • DbSet.Local

  • DbContext.SaveChanges

  • Running any LINQ query against a DbSet

There are more methods that will trigger DetectChanges. You’ll learn more about these methods throughout the rest of this book:

  • DbSet.Attach

  • DbContext.GetValidationErrors

  • DbContext.Entry

  • DbChangeTracker.Entries

Controlling When DetectChanges Is Called

The most obvious time that Entity Framework needs to know about changes is during SaveChanges, but there are also many others. For example, if we ask the change tracker for the current state of an object, it will need to scan and check if anything has changed. Scanning isn’t just restricted to the object in question either. Consider a situation where you query for a Lodging from the database and then add it to the Lodgings collection of a new Destination. This Lodging is now modified because assigning it to a new Destination changes its DestinationId property. But to know that this change has occurred (or hasn’t occurred) Entity Framework needs to scan all of the Destination objects as well. Many of the operations you perform on the DbContext API will cause DetectChanges to be run.

In most cases DetectChanges is fast enough that it doesn’t cause performance issues. However, if you have a very large number of objects in memory or you are performing a lot of operations on DbContext in quick succession, the automatic DetectChanges behavior may be a performance concern. Fortunately you have the option to switch off the automatic DetectChanges behavior and call it manually when you know that it needs to be called.

Entity Framework is built on the assumption that you will call DetectChanges before every API call if you have changed any of the entities since the last API call. This includes calling DetectChanges before running any queries. Failure to do this can result in unexpected side effects. DbContext takes care of this requirement for you provided that you leave automatic DetectChanges enabled. If you switch it off, you are responsible for calling DetectChanges.

Warning

Working out when DetectChanges needs to be called isn’t as trivial as it may appear. The Entity Framework team strongly recommends that you only swap to manually calling DetectChanges if you are experiencing performance issues. It’s also recommended to only opt out of automatic DetectChanges for poorly performing sections of code and to reenable it once the section in question has finished executing.

Automatic DetectChanges can be toggled on and off via the DbContext.Configuration.AutoDetectChangesEnabled Boolean flag. Let’s add a ManualDetectChanges method that disables automatic DetectChanges and observes the effect this has (Example 3-14).

Example 3-14. Manually calling DetectChanges
private static void ManualDetectChanges()
{
  using (var context = new BreakAwayContext())
  {
    context.Configuration.AutoDetectChangesEnabled = false;

    var reef = (from d in context.Destinations
                where d.Name == "Great Barrier Reef"
                select d).Single();

    reef.Description = "The world's largest reef.";

    Console.WriteLine(
      "Before DetectChanges: {0}",
      context.Entry(reef).State);

    context.ChangeTracker.DetectChanges();

    Console.WriteLine(
      "After DetectChanges: {0}",
      context.Entry(reef).State);
  }
}

The code switches off automatic DetectChanges and then queries for the Great Barrier Reef and changes its description. The next line will write out the current state that the context thinks the reef entity is in. We then manually call DetectChanges and repeat the process of writing out the current state. Accessing the current state makes use of the Entry method from Change Tracker API, which is discussed in Chapter 5. If you update the Main method to call ManualDetectChanges, you will see the following output:

Before DetectChanges: Unchanged
After DetectChanges: Modified

As expected, the context doesn’t detect that the reef entity is modified until after we manually call DetectChanges. The reason we get an incorrect result is that we broke the rule of calling DetectChanges before calling an API after we had modified an entity. Because we were simply reading the state of an entity, this didn’t have any nasty side effects.

The code we saw in Example 3-9 didn’t really buy us anything by switching off automatic DetectChanges. Calling the DbContext.Entry method would also have automatically triggered DetectChanges if the change tracking wasn’t disabled.

Note

If you write tests to check the state of entities and you use this Entry method to inspect state, keep in mind that the Entry method itself calls DetectChanges. This could inadvertently alter your test results. You can use the AutoDetectChangesEnabled configuration to have tighter control over DetectChanges in this scenario.

However if we were performing a series of API calls on DbContext without changing any objects in between, we could avoid some unnecessary execution of the DetectChanges process. The AddMultipleDestinations method shown in Example 3-15 demonstrates this.

Example 3-15. Adding multiple objects without DetectChanges
private static void AddMultipleDestinations()
{
  using (var context = new BreakAwayContext())
  {
    context.Configuration.AutoDetectChangesEnabled = false;

    context.Destinations.Add(new Destination
    {
        Name = "Paris",
        Country = "France"
    });

    context.Destinations.Add(new Destination
    {
      Name = "Grindelwald",
      Country = "Switzerland"
    });

    context.Destinations.Add(new Destination
    {
      Name = "Crete",
      Country = "Greece"
    });

    context.SaveChanges();
  }
}

This code avoids four unnecessary calls to DetectChanges that would have occurred while calling the DbSet.Add and SaveChanges methods. This example is used purely for demonstration purposes and is not a scenario where disabling DetectChanges is going to provide any significant benefit. In Chapter 5 you’ll learn about making changes to your objects using the Change Tracker API. The Change Tracker API enables you to make changes to your objects by going through a DbContext API. This approach allows you to change your objects without the need to call DetectChanges.

Using DetectChanges to Trigger Relationship Fix-up

DetectChanges is also responsible for performing relationship fix-up for any relationships that it detects have changed. If you have changed some relationships and would like to have all the navigation properties and foreign key properties synchronized, DetectChanges will achieve this. This can be particularly useful in data-binding scenarios where your UI will change one of the navigation properties (or perhaps the foreign key property) but you then want the other properties in the relationship to be updated to reflect the change. The DetectRelationshipChanges method in Example 3-16 uses DetectChanges to perform relationship fix-up.

Example 3-16. Using DetectChanges to fix up relationships
private static void DetectRelationshipChanges()
{
  using (var context = new BreakAwayContext())
  {
    var hawaii = (from d in context.Destinations
                where d.Name == "Hawaii"
                select d).Single();

    var davesDump = (from l in context.Lodgings
                     where l.Name == "Dave's Dump"
                     select l).Single();

    context.Entry(davesDump)
      .Reference(l => l.Destination)
      .Load();

    hawaii.Lodgings.Add(davesDump);

    Console.WriteLine(
      "Before DetectChanges: {0}",
      davesDump.Destination.Name);

    context.ChangeTracker.DetectChanges();

    Console.WriteLine(
      "After DetectChanges: {0}",
      davesDump.Destination.Name);
  }
}

The code loads the Hawaii Destination into memory as well as Dave’s Dump Lodging. It also uses explicit loading to load the Destination of Dave’s Dump—that’s the Grand Canyon. Dave’s Dump has such a bad reputation that he has decided to move the Dump to Hawaii where nobody’s heard of him yet. So we add the davesDump instance to the Lodgings collection of hawaii. Because we are using POCO objects, Entity Framework doesn’t know that we’ve made this change, and therefore it doesn’t fix up the navigation property or foreign key property on davesDump. We could wait until we call SaveChanges, or any other method, which triggers DetectChanges, but perhaps we want things fixed up right away. We’ve added in a call to DetectChanges to achieve this. If we update the Main method to call DetectRelationshipChanges and run the application we see this in action:

Before DetectChanges: Grand Canyon
After DetectChanges: Hawaii

Before the DetectChanges call, Dave’s Dump is still assigned to the old Destination. After we call DetectChanges, relationship fix-up has occurred and everything is back in sync.

Enabling and Working with Change Tracking Proxies

If your performance profiler has pinpointed excessive calls to DetectChanges as a problem or you prefer relationship fix-up to occur in real time, there is another option—the change tracking proxies mentioned earlier. With only some minor changes to your POCO classes, Entity Framework will be able to create change tracking proxies. Change tracking proxies will allow Entity Framework to track changes as we make them to our objects and also perform relationship fix-up as it detects changes to relationships.

The rules for allowing a change tracking proxy to be created are as follows:

  • The class must be public and not sealed.

  • Each property must be marked as virtual.

  • Each property must have a public getter and setter.

  • Any collection navigation properties must be typed as ICollection<T>.

Update the Destination class as shown in Example 3-17 to meet these requirements. Notice that we are also removing the logic from the constructor that initialized the Lodgings property. The change tracking proxy will override any collection navigation properties and use its own collection type (EntityCollection<TEntity>). This collection type will track any changes to the collection and report them to the change tracker. If you attempt to assign another type to the property, such as the List<T> we were creating in the constructor, the proxy will throw an exception.

Example 3-17. Destination class updated to enable change tracking proxies
[Table("Locations", Schema = "baga")]
public class Destination
{
  public Destination()
  {
    //this.Lodgings = new List<Lodging>();
  }

  [Column("LocationID")]
  public virtual int DestinationId { get; set; }
  [Required, Column("LocationName")]
  [MaxLength(200)]
  public virtual string Name { get; set; }
  public virtual string Country { get; set; }
  [MaxLength(500)]
  public virtual string Description { get; set; }
  [Column(TypeName = "image")]
  public virtual byte[] Photo { get; set; }
  public virtual string TravelWarnings { get; set; }
  public virtual string ClimateInfo { get; set; }

  public virtual ICollection<Lodging> Lodgings { get; set; }
}

In Chapter 2, you learned how Entity Framework creates dynamic proxies for a class when one or more navigation properties in that class are marked virtual. Those proxies, which derive from the given class, allow the virtual navigation properties to be lazy loaded. The change tracking proxies are created in the same way at runtime, but these proxies have more features than those you saw in Chapter 2.

While the requirements for getting a change tracking proxy are fairly simple, it’s also very easy to miss one of them. It’s even easier to make a change to the class in the future that will unintentionally break one of the rules. Because of this, it’s a good idea to add a unit test that ensures Entity Framework can create a change tracking proxy. Let’s add a method that will test just this (Example 3-18). You’ll also need to add a using for the System.Data.Objects.DataClasses namespace.

Example 3-18. Testing for a change tracking proxy
private static void TestForChangeTrackingProxy()
{
  using (var context = new BreakAwayContext())
  {
    var destination = context.Destinations.First();

    var isProxy = destination is IEntityWithChangeTracker;

    Console.WriteLine("Destination is a proxy: {0}", isProxy);
  }
}

When Entity Framework creates the dynamic proxy for change tracking, it will implement the IEntityWithChangeTracker interface. The test in Example 3-18 creates a Destination instance by retrieving it from the database and then checks for this interface to ensure that the Destination is wrapped with a change tracking proxy. Note that it’s not enough just to check that Entity Framework is creating a proxy class that derives from our class, because lazy loading proxies will also do this. The presence of IEntityWithChangeTracker is what causes Entity Framework to listen for changes in real time.

Now that we have a change tracking proxy, let’s update Main to call the ManualDetectChanges method we wrote back in Example 3-14 and run the application:

Before DetectChanges: Modified
After DetectChanges: Modified

This time we see that Entity Framework is aware of changes regardless of whether DetectChanges is called or not. Now update Main to call the DetectRelationshipChanges method we wrote in Example 3-16 and run the application:

Before DetectChanges: Hawaii
After DetectChanges: Hawaii

This time we see that Entity Framework detected the relationship change and performed relationship fix-up without DetectChanges being called.

Note

It is not necessary to disable automatic DetectChanges when you use change tracking proxies. DetectChanges will skip the change detection process for any objects that report changes in real time. Therefore, enabling change tracking proxies is enough to get the performance benefits of avoiding DetectChanges. In fact, Entity Framework won’t even take a snapshot of the property values when it finds a change tracking proxy. DetectChanges knows it can skip scanning for changes in entities that don’t have a snapshot of their original values.

Warning

If you have entities that contain complex types (for example, Person.Address), Entity Framework will still use snapshot change tracking for the properties contained in the complex type. This is required because Entity Framework does not create a proxy for the complex type instance. You still get the benefits of automatic change detection on the properties defined directly on the entity itself, but changes to properties on the complex type will only be detected by DetectChanges.

Ensuring the New Instances Get Proxies

Entity Framework will automatically create proxies for the results of any queries you run. However, if you just use the constructor of your POCO class to create new objects, these will not be proxies. In order to get proxies you need to use the DbSet.Create method to get new instances of an entity. This rule is the same as when working with POCOs with ObjectContext and ObjectSet.

Note

If you have enabled change tracking proxies for an entity in your model, you can still create and add nonproxy instances of the entity. Entity Framework will happily work with a mixture of proxied and nonproxied entities in the same set. You just need to be aware that you will not get automatic change tracking or relationship fix-up for instances that are not change tracking proxies. Having a mixture of proxied and nonproxied instances in the same set can be confusing, so it’s generally recommended that you use DbSet.Create to create new instances so that all entities in the set are change tracking proxies.

Add the CreatingNewProxies method shown in Example 3-19 to see this in action.

Example 3-19. Creating new proxy instances
private static void CreatingNewProxies()
{
  using (var context = new BreakAwayContext())
  {
    var nonProxy = new Destination();
    nonProxy.Name = "Non-proxy Destination";
    nonProxy.Lodgings = new List<Lodging>();

    var proxy = context.Destinations.Create();
    proxy.Name = "Proxy Destination";

    context.Destinations.Add(proxy);
    context.Destinations.Add(nonProxy);

    var davesDump = (from l in context.Lodgings
                     where l.Name == "Dave's Dump"
                     select l).Single();

    context.Entry(davesDump)
      .Reference(l => l.Destination)
      .Load();

    Console.WriteLine(
      "Before changes: {0}",
      davesDump.Destination.Name);

    nonProxy.Lodgings.Add(davesDump);

    Console.WriteLine(
      "Added to non-proxy destination: {0}",
      davesDump.Destination.Name);

    proxy.Lodgings.Add(davesDump);

    Console.WriteLine(
      "Added to proxy destination: {0}",
      davesDump.Destination.Name);
  }
}

The code starts by creating two new Destination instances and adding them to the Destinations set on the context. One of these Destinations is just an instance of our POCO class. The other is created using DbSet.Create and is a change tracking proxy. Next, the code queries for Dave’s Dump and loads the Destination that it currently belongs to using the Entry method that you learned about in Chapter 2. We then add Dave’s Dump to the Lodgings property of the POCO Destination and then to the proxy Destination. At each stage the code prints out the name of the Destination assigned to the Destination property on Dave’s Dump:

Before changes: Grand Canyon
Added to non-proxy destination: Grand Canyon
Added to proxy destination: Proxy Destination

You can see that Dave’s Dump is initially assigned to the Grand Canyon Destination. When it’s added to the Lodgings collection of the nonproxy Destination, the Destination property on Dave’s Dump is not updated; it’s still the Grand Canyon. Because this Destination isn’t a proxy, Entity Framework isn’t aware that we changed the relationship. However, when we add it to the Lodgings collection of the proxy Destination we get full relationship fix-up instantly.

Creating Proxy Instances for Derived Types

There is also a generic overload of DbSet.Create that is used to create instances of derived classes in our set. For example, calling Create on the Lodgings set will give you an instance of the Lodging class. But the Lodgings set can also contain instances of Resort, which derives from Lodging. To get a new proxy instance of Resort, we use the generic overload:

var newResort = context.Lodgings.Create<Resort>();

Fetching Entities Without Change Tracking

You’ve probably gathered by now that tracking changes isn’t a trivial process and there is a bit of overhead involved. In some areas of your application, you may be displaying data in a read-only screen. Because the data will never get updated, you may want to avoid the overhead associated with change tracking.

Fortunately Entity Framework includes an AsNoTracking method that can be used to execute a no-tracking query. A no-tracking query is simply a query where the results will not be tracked for changes by the context. Add the PrintDestinationsWithoutChangeTracking method shown in Example 3-20.

Example 3-20. Querying data without change tracking
private static void PrintDestinationsWithoutChangeTracking()
{
  using (var context = new BreakAwayContext())
  {
    foreach (var destination in context.Destinations.AsNoTracking())
    {
      Console.WriteLine(destination.Name);
    }
  }
}

The code uses the AsNoTracking method to get a no-tracking query for the contents of the Destinations set. The results are then iterated over and printed to the console. Because this is a no-tracking query, the context is not keeping track of any changes made to the Destinations. If you were to modify a property of one of the Destinations and call SaveChanges, the changes would not be sent to the database.

Note

Fetching data without change tracking will usually only provide a noticeable performance gain when you are fetching larger amounts of data for read-only display. If your application will update and save any of the data, you should not use AsNoTracking there.

AsNoTracking is an extension method defined on IQueryable<T>, so you can use it in LINQ queries also. You can use AsNoTracking on the end of the DbSet in the from line of the query:

var query = from d in context.Destinations.AsNoTracking()
            where d.Country == "Australia"
            select d;

You can also use AsNoTracking to convert an existing LINQ query to be a no-tracking query. Note that the code doesn’t just call AsNoTracking on the existing query but overrides the query variable with the result of the AsNoTracking call. This is required because AsNoTracking doesn’t modify the query that it is called on, it returns a new query:

var query = from d in context.Destinations
            where d.Country == "Australia"
            select d;

query = query.AsNoTracking();

Because AsNoTracking is an extension method, you will need to have the System.Data.Entity namespace imported to use it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset