Chapter 5. Change Tracker API

So far you have seen how to use Entity Framework to query for data from the database and save changes to those entities back to the database. You’ve seen how Entity Framework will keep track of any changes you make to entities that are being tracked by a context. It is the responsibility of the Change Tracker to keep track of these changes as you make them.

In this chapter you will learn about using the Change Tracker API to access the information that Entity Framework is storing about the entities it is tracking. This information goes beyond the values stored in the properties of your entities and includes the current state of the entity, the original values from the database, which properties have been modified, and other data. The Change Tracker API also gives you access to additional operations that can be performed on an entity, such as reloading its values from the database to ensure you have the latest data.

You’ve already seen bits of the Change Tracker API in action in earlier chapters. In Chapter 2 you saw how to perform explicit loading using the DbContext.Entry. In Chapter 3 you saw how to get the Change Tracker to scan your entities for changes using the DbContext.ChangeTracker.DetectChanges method. In Chapter 4 you saw how to set the state of an entity, mark individual properties as modified, and work with original values using the Entry method. You also saw how to look at all entities being tracked by the context using the DbContext.ChangeTracker.Entries method.

We’ll start this chapter by taking a tour of all the information and operations that are available in the Change Tracker API. Then we’ll wrap up the chapter by looking at how these operations can be combined to save time logging and resolving concurrency conflicts.

Change Tracking Information and Operations for a Single Entity

The easiest way to get access to the change tracking information for an entity is using the Entry method on DbContext. Entry returns a DbEntityEntry instance, which gives you access to the information and operations available for the entity. There are two overloads of Entry. One is generic (Entry<TEntity>) and will return a strongly typed DbEntityEntry<TEntity>:

public DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity)

The other overload is nongeneric and returns DbEntityEntry:

public DbEntityEntry Entry(object entity);

Both of these provide access to exactly the same information and operations. Because the strongly typed DbEntityEntry<TEntity> knows the type of entity it represents, it allows you to use lambda expressions when drilling into property details, so that you get IntelliSense and additional compile-time checks. You don’t need to worry about selecting the correct overload—the compiler will take care of this for you. If the entity you pass to Entry is typed as object, you will get the nongeneric DbEntityEntry. If the entity you pass in is typed as something more specific than object (for example, Destination), you will get the generic DbEntityEntry<TEntity>, where TEntity is the same type as the entity you pass in. You’ll see both of these overloads in action in the next couple of sections.

Working with the State Property

One of the core pieces of change tracking information is what state the entity is currently in: Added, Unchanged, Modified, or Deleted. This can be determined using the State property on DbEntityEntry. To see how this works, add the PrintState method shown in Example 5-1.

Example 5-1. Reading the State property
private static void PrintState()
{
  using (var context = new BreakAwayContext())
  {
    var canyon = (from d in context.Destinations
                  where d.Name == "Grand Canyon"
                  select d).Single();

    DbEntityEntry<Destination> entry = context.Entry(canyon);

    Console.WriteLine("Before Edit: {0}", entry.State);
    canyon.TravelWarnings = "Take lots of water.";
    Console.WriteLine("After Edit: {0}", entry.State);
  }
}

The code retrieves the Grand Canyon destination from the database and then locates the change tracking entry for it. The canyon variable is strongly typed as Destination, so the compiler selects the generic overload of Entry and we get a strongly typed DbEntityEntry<Destination> returned. The code then prints out the state of canyon as recorded in the change tracker. Next, the TravelWarnings property is modified and then the State is printed out again. Update the Main method to call the PrintState method and run the application. The console window will display the following:

Before Edit: Unchanged
After Edit: Modified

As expected, the canyon object is reported as Unchanged after it is retrieved from the database. After modifying one of its properties, the object is seen by the change tracker as being in the Modified state.

Back in Chapter 3, we enabled Destination as a change tracking proxy, meaning that changes to any instances of Destination are reported to the context in real time. Entities that are not change tracking proxies require an explicit or implicit call to DetectChanges to scan for any changes to the object. Most of the methods on DbContext will automatically call DetectChanges for you. Entry is one of those methods. But reading the State property will not cause an automatic DetectChanges. If Destination was not a change tracking proxy, you would need to call DetectChanges after setting the TravelWarnings property to get the correct state reported. More information on DetectChanges is available in the Using Snapshot Change Tracking section of Chapter 3. You can avoid the need to call DetectChanges by calling Entry each time you need the entry, rather than keeping a reference to it. For example, rather than storing the entry in the entry variable as you did in Example 5-1, you could use Entry each time you want the state:

Console.WriteLine("Before Edit: {0}", context.Entry(canyon).State);
canyon.TravelWarnings = "Take lots of water.";
Console.WriteLine("After Edit: {0}", context.Entry(canyon).State);

Note

The State property also exposes a public setter, meaning you can assign a state to an entity. Setting the state is useful when you are working with disconnected graphs of entities—typically in N-Tier scenarios. Chapter 4 of this book is dedicated to learning about the various ways to set the state of entities, including setting the State property.

Working with Current, Original, and Database Values

Along with the current state of an entity, DbEntityEntry gives you access to the entity’s current, original, and database values. The DbPropertyValues type is used to represent each of these sets of properties. Current values are the values that are currently set in the properties of the entity. Original values are the values for each property when the entity was originally attached to the context; for example, when the entity was first retrieved from the database. Database values are the values currently stored in the database, which may have changed since you queried for the entity. Accessing database values involves Entity Framework performing a behind-the-scenes query for you.

Warning

There is a bug in Entity Framework 4.1 and 4.2 that blocks you from using the GetDatabaseValues method for an entity that is not in the same namespace as your context. The Entity Framework team has fixed this bug in the Entity Framework 4.3 release. If you are using Entity Framework 4.2 or earlier, you will need to modify the namespace of your context class to be the same as your domain classes. Failure to make this change will result in an EntitySqlException if you attempt to retrieve the database values for an entity.

Let’s start by writing a method that will output these various values for any given Lodging. Add the PrintChangeTrackingInfo method shown in Example 5-2.

Example 5-2. Printing change tracking info for a Lodging
private static void PrintChangeTrackingInfo(
  BreakAwayContext context,
  Lodging entity)
{
  var entry = context.Entry(entity);

  Console.WriteLine(entry.Entity.Name);

  Console.WriteLine("State: {0}", entry.State);

  Console.WriteLine("
Current Values:");
  PrintPropertyValues(entry.CurrentValues);

  Console.WriteLine("
Original Values:");
  PrintPropertyValues(entry.OriginalValues);

  Console.WriteLine("
Database Values:");
  PrintPropertyValues(entry.GetDatabaseValues());
}

private static void PrintPropertyValues(DbPropertyValues values)
{
  foreach (var propertyName in values.PropertyNames)
  {
    Console.WriteLine(" - {0}: {1}",
      propertyName,
      values[propertyName]);
  }
}

The code starts by looking up the change tracking entry for the supplied Lodging. Because the lodging variable is strongly typed as Lodging, the compiler will select the generic overload of Entry. The code then prints out the Name of the Lodging. Of course, we could have just gotten the name from the lodging variable, but we are using the Entity property on the entry for demonstration purposes. Because the entry is strongly typed, the Entity property provides strongly typed access to the Destination, which is why we can call entry.Entity.Name.

The code then loops through the current, original, and database values and writes the value for each property using the PrintPropertyValues helper method. These collections of values are all the DbPropertyValues type. Note that there is no way to directly iterate over the values, so you need to iterate over the names of the properties and look up the value for each property. GetDatabaseValues will send a query to the database at the time it is called, to determine what values are currently stored in the database. A new query will be sent to the database every time you call the method.

Note

This method for accessing current, original, and database values uses a string to identify the property to get the value for. In Working with Individual Properties, you will learn about a strongly typed way to specify the property.

Now let’s write a method to test out our change tracking logic. Go ahead and add the PrintLodgingInfo method shown in Example 5-3.

Example 5-3. Method to test PrintChangeTrackingInfo
private static void PrintLodgingInfo()
{
  using (var context = new BreakAwayContext())
  {
    var hotel = (from d in context.Lodgings
                      where d.Name == "Grand Hotel"
                      select d).Single();

    hotel.Name = "Super Grand Hotel";

    context.Database.ExecuteSqlCommand(
      @"UPDATE Lodgings
        SET Name = 'Not-So-Grand Hotel'
        WHERE Name = 'Grand Hotel'");

    PrintChangeTrackingInfo(context, hotel);
  }
}

This new method locates an existing Lodging from the database and modifies its Name property. The code then uses Database.ExecuteSqlCommand to run some raw SQL to modify the name of the Lodging in the database. You’ll learn more about executing raw SQL against the database in Chapter 8. Finally, the hotel instance is passed to our PrintChangeTrackingInfo method. Update the Main method to call PrintLodgingInfo and run the application, which will output the following to the console:

Super Grand Hotel
State: Modified

Current Values:
 - LodgingId: 1
 - Name: Super Grand Hotel
 - Owner:
 - MilesFromNearestAirport: 2.50
 - DestinationId: 1
 - PrimaryContactId:
 - SecondaryContactId:

Original Values:
 - LodgingId: 1
 - Name: Grand Hotel
 - Owner:
 - MilesFromNearestAirport: 2.50
 - DestinationId: 1
 - PrimaryContactId:
 - SecondaryContactId:

Database Values:
 - LodgingId: 1
 - Name: Not-So-Grand Hotel
 - Owner:
 - MilesFromNearestAirport: 2.50
 - DestinationId: 1
 - PrimaryContactId:
 - SecondaryContactId:

As expected, the current name of the hotel is printed out. Since we changed the Name, the State of the entity is displayed as Modified. The current value for Name shows the new name that we assigned in PrintLodgingInfo (“Super Grand Hotel”). The original value of Name shows the value when we retrieved the Lodging from the database (“Grand Hotel”). The database value of Name shows the new value we assigned in the database using the raw SQL command, after the hotel was retrieved from the database (“Not-So-Grand Hotel”).

The code from Example 5-2 works nicely for existing entities, because they have current, original, and database values. But this isn’t true for new entities or entities that are marked for deletion. New entities don’t have original values or database values. Entity Framework doesn’t track current values for entities that are marked for deletion. If you try to access such values, Entity Framework will throw an exception. Let’s add some conditional logic in PrintChangeTrackingInfo to account for these restrictions. Replace the code that prints out current, original, and database values with the code in Example 5-4.

Example 5-4. Avoiding trying to access invalid values
if (entry.State != EntityState.Deleted)
{
  Console.WriteLine("
Current Values:");
  PrintPropertyValues(entry.CurrentValues);
}

if (entry.State != EntityState.Added)
{
  Console.WriteLine("
Original Values:");
  PrintPropertyValues(entry.OriginalValues);

  Console.WriteLine("
Database Values:");
  PrintPropertyValues(entry.GetDatabaseValues());
}

The updated code now checks the State of the entry and skips printing out current values for entities that are marked for deletion. The code also skips printing original and database values for newly added entities. Let’s also update the PrintLodgingInfo method so that we can see these changes in action (Example 5-5).

Example 5-5. Modified PrintLodgingInfo method
private static void PrintLodgingInfo()
{
  using (var context = new BreakAwayContext())
  {
    var hotel = (from d in context.Lodgings
                      where d.Name == "Grand Hotel"
                      select d).Single();

    PrintChangeTrackingInfo(context, hotel);

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

    context.Lodgings.Remove(davesDump);

    PrintChangeTrackingInfo(context, davesDump);

    var newMotel = new Lodging { Name = "New Motel" };

    context.Lodgings.Add(newMotel);

    PrintChangeTrackingInfo(context, newMotel);
  }
}

The updated code now also locates Dave’s Dump and marks it for deletion. The code also adds New Motel to the context. PrintChangeTrackingInfo is called for both of these entities. If you run the application again, you will see that the relevant change tracking information is successfully displayed for all three locations.

The PrintChangeTrackingInfo method will currently only work for Lodgings, because the entity parameter is strongly typed. But most of the code in the method is not specific to the Lodging type. Let’s change the PrintChangeTrackingInfo method to accept any entity type by changing the entity parameter to be typed as object rather than Lodging:

private static void PrintChangeTrackingInfo(
  BreakAwayContext context,
  object entity)

Since the entity may not be a Lodging, the compiler now selects the nongeneric Entry method, which returns the nongeneric DbEntityEntry. Because we no longer know what type the entity is, we can’t be sure that there is a Name property to print—in fact, the compiler will give us an error if we try to. Remove the line that printed out the name of the Lodging and replace it with a line that prints out the type of the entity instead:

Console.WriteLine("Type: {0}", entry.Entity.GetType());

Go ahead and run the application. You will see that the updated code continues to successfully display change tracking information.

So far, our examples have used the indexer syntax to get the value for a property out of DbPropertyValues. The indexer syntax is where you used square braces on an object to specify the key/index of the value you want to retrieve (in other words, entry.CurrentValues[“Name”]). Because there is no way to know what type will be returned from each property, the return type of the indexer on DbPropertyValues is object. There may be times where you do know what type the value will be. Rather than casting the return value to the required type, you can use the GetValue<TValue> method to specify the type of the value. For example, you may want to find the original value that was assigned to the Name property of a Lodging when it was retrieved from the database. Add the PrintOriginalName method shown in Example 5-6.

Example 5-6. Using GetValue<TValue> to get a strongly typed original value
private static void PrintOriginalName()
{
  using (var context = new BreakAwayContext())
  {
    var hotel = (from d in context.Lodgings
                  where d.Name == "Grand Hotel"
                  select d).Single();

    hotel.Name = "Super Grand Hotel";

    string originalName = context.Entry(hotel)
      .OriginalValues
      .GetValue<string>("Name");

    Console.WriteLine("Current Name: {0}", hotel.Name);
    Console.WriteLine("Original Name: {0}", originalName);
  }
}

The code retrieves a Lodging from the database and changes its Name property. The code then looks up the original value for the Name property using the GetValue method on the OriginalValues for the entity. Because we know that Name is a string property, the code specifies string as the TValue when calling GetValues. The original value is returned as a string and the current and original value for the Name property are then printed to the console.

Working with DbPropertyValues for Complex Types

Let’s look at how you can work with DbPropertyValues when you have a property on your entity that uses a complex type. Remember that complex types allow you to group multiple scalar values into a class. A property that references a complex type is known as a complex property. For example, the BAGA model uses an Address complex type to group address related properties (Example 5-7).

Example 5-7. The existing Address classes
[ComplexType]
public class Address
{
  public int AddressId { get; set; }
  [MaxLength(150)]
  [Column("StreetAddress")]
  public string StreetAddress { get; set; }
  [Column("City")]
  public string City { get; set; }
  [Column("State")]
  public string State { get; set; }
  [Column("ZipCode")]
  public string ZipCode { get; set; }
}

Note

Code First convention recognizes complex types when the type has no key property. Since Address has a property that Code First will recognize as a key, AddressId, and therefore will infer this to be an entity type, the class is explicitly marked as a ComplexType.

This complex type is then used by the Address property in the Person class (Example 5-8). Person.Address is therefore a complex property. Note that PersonInfo is also a complex type.

Example 5-8. The existing Person Class
[Table("People")]
public class Person
{
  public Person()
  {
    Address = new Address();
    Info = new PersonalInfo
    {
      Weight = new Measurement(),
      Height = new Measurement()
    };
  }

  public int PersonId { get; set; }
  [ConcurrencyCheck]
  public int SocialSecurityNumber { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public Address Address { get; set; }
  public PersonalInfo Info { get; set; }

  public List<Lodging> PrimaryContactFor { get; set; }
  public List<Lodging> SecondaryContactFor { get; set; }
  [Required]
  public PersonPhoto Photo { get; set; }
  public List<Reservation> Reservations { get; set; }
}

To demonstrate how DbPropertyValues handles complex types, let’s create a new Person and pass it to our PrintChangeTrackingInfo method. Add the PrintPersonInfo method shown in Example 5-9.

Example 5-9. Printing change tracking information for an entity with a complex property
private static void PrintPersonInfo()
{
  using (var context = new BreakAwayContext())
  {
    var person = new Person
    {
      FirstName = "John",
      LastName = "Doe",
      Address = new Address { State = "VT" }
    };

    context.People.Add(person);

    PrintChangeTrackingInfo(context, person);
  }
}

When we get the value for a complex property from DbPropertyValues, it’s going to return another DbPropertyValues that contains the values from the complex type. Let’s update the PrintPropertyValues helper method to account for this (Example 5-10).

Example 5-10. PrintPropertyValues updated to account for complex properties
private static void PrintPropertyValues(
  DbPropertyValues values,
  int indent = 1)
{
  foreach (var propertyName in values.PropertyNames)
  {
    var value = values[propertyName];
    if (value is DbPropertyValues)
    {
      Console.WriteLine(
        "{0}- Complex Property: {1}",
        string.Empty.PadLeft(indent),
        propertyName);

      PrintPropertyValues(
        (DbPropertyValues)value,
        indent + 1);
    }
    else
    {
      Console.WriteLine(
        "{0}- {1}: {2}",
        string.Empty.PadLeft(indent),
        propertyName,
        values[propertyName]);
    }
  }
}

For each property being printed, the code now checks if the value is a DbPropertyValues. If it is, the code prints out the name of the complex property and then recursively calls PrintPropertyValues with the values for the complex type. PrintPropertyValues also allows an indent level to be supplied, which is used to indent the values of a complex property. If an indent is not supplied, a default indent of 1 is used. Update the Main method to call PrintPersonInfo and run the application. The console will display the following output:

Type: Model.Person
State: Added

Current Values:
  - PersonId: 0
  - SocialSecurityNumber: 0
  - FirstName: John
  - LastName: Doe
  - Complex Property: Address
   - AddressId: 0
   - StreetAddress:
   - City:
   - State: VT
   - ZipCode:
  - Complex Property: Info
   - Complex Property: Weight
    - Reading: 0
    - Units:
   - Complex Property: Height
    - Reading: 0
    - Units:
    - DietryRestrictions:

The console output is displaying the property values contained in the Address property. You’ll also notice that the code works for nested complex types. Person.Info is a complex property that references the PersonInfo complex type. PersonInfo also has complex properties for a Person’s Height and Weight. From the printout you can see that the DbPropertyValues for the Info complex property returned another DbPropertyValues for its Weight and Height properties.

Copying the Values from DbPropertyValues into an Entity

Having a single object, like DbPropertyValues, that represents a set of values is handy. However, we usually want to write application logic in terms of our domain classes, rather than a type such as DbPropertyValues. For example, we might have a method that defines how we display a Destination for the user of our application to see. We can pass any instance of a Destination into the method to print out its current values, but it would be good if we could use that same method to print out the original and database values as well. Add the PrintDestination method shown in Example 5-11.

Example 5-11. Method to print information about a Destination
private static void PrintDestination(Destination destination)
{
  Console.WriteLine("-- {0}, {1} --",
    destination.Name,
    destination.Country);

  Console.WriteLine(destination.Description);

  if (destination.TravelWarnings != null)
  {
    Console.WriteLine("WARNINGS!: {0}", destination.TravelWarnings);
  }
}

If you want to display the current values for a Destination, you can pass the actual instance to the method. But there may be scenarios where you want to display the original values fetched from the database or perhaps the current database values to the end user. One such scenario is resolving concurrency conflicts during a save. We’ll look at that particular scenario in more detail later in this chapter.

DbPropertyValues includes a ToObject method that will copy the values into a new instance of the entity without overwriting the existing instance as you would with a query to the database. To see how this works, add the TestPrintDestination method shown in Example 5-12.

Example 5-12. Getting an entity representing the values in the database
private static void TestPrintDestination()
{
  using (var context = new BreakAwayContext())
  {
    var reef = (from d in context.Destinations
                where d.Name == "Great Barrier Reef"
                select d).Single();

    reef.TravelWarnings = "Watch out for sharks!";

    Console.WriteLine("Current Values");
    PrintDestination(reef);

    Console.WriteLine("
Database Values");
    DbPropertyValues dbValues = context.Entry(reef)
      .GetDatabaseValues();

    PrintDestination((Destination)dbValues.ToObject());
  }
}

The code retrieves the Great Barrier Reef Destination from the database and changes its TravelWarnings property. Then it passes the Destination to the PrintDestination method to print out the current values. Next, it gets the values from the database and uses ToObject to construct a Destination that contains the values from the database. This new Destination is then passed to PrintDestination to print the database values. Update the Main method to call TestPrintDestination and run the application:

Current Values
-- Great Barrier Reef, Australia --
Beautiful coral reef.
WARNINGS!: Watch out for sharks!

Database Values
-- Great Barrier Reef, Australia --
Beautiful coral reef

The current and database values are printed to the screen and you can see that the updated TravelWarnings property is printed out in the current values. The second instance of Destination, which was created by calling ToObject, is not attached to the context. Any changes to this second instance will not be persisted during SaveChanges.

Note

ToObject will only clone the values from scalar properties; all navigation properties on the entity will be left unassigned. This makes ToObject useful for cloning a single object, but it will not clone an entire object graph for you.

Changing Values in a DbPropertyValues

DbPropertyValues isn’t a read-only type. You can also use it to update values that are stored in an instance. When you set values using CurrentValues or OriginalValues, this will also update the current and original values in the change tracker. Additionally, updating the CurrentValues will change the values that are stored in the properties of your entity instance.

In Recording Original Values, you saw how the OriginalValues could be individually updated. As each value was updated, the Change Tracker worked out which properties had been modified. Let’s take a look at setting the current values. Add the ChangeCurrentValue method shown in Example 5-13.

Example 5-13. Changing a current value via the Change Tracker API
private static void ChangeCurrentValue()
{
  using (var context = new BreakAwayContext())
  {
    var hotel = (from d in context.Lodgings
                 where d.Name == "Grand Hotel"
                 select d).Single();

    context.Entry(hotel)
      .CurrentValues["Name"] = "Hotel Pretentious";

    Console.WriteLine("Property Value: {0}", hotel.Name);
  }
}

The code loads the Grand Hotel Lodging from the database. The code then gets the CurrentValues for the hotel instance and modifies the value stored for the Name property. Finally, the code writes out the value stored in the Name property on the entity. Update the Main method to call ChangeCurrentValue and run the application, which will result in the following output:

Property Value: Hotel Pretentious

We see from the output that updating the value of a property in the CurrentValues has also updated the value stored in the property of the entity.

Back in Working with Change Tracking, you learned that POCO entities require a call to DetectChanges to scan for changes in the properties of the entity. You also learned that DbContext takes care of calling DetectChanges for you, but that you can disable this behavior if you want to control when DetectChanges is called.

Note

Remember that in most cases it is best to let DbContext automatically call DetectChanges for you. More information on manually calling DetectChanges and the use of change tracking proxies is available in Chapter 4.

If you make changes using the Change Tracker API, there is no need for DetectChanges to be called, because the Change Tracker is aware of the change being made. To see this in action, update the ChangeCurrentValue method, as shown in Example 5-14.

Example 5-14. Updating via the Change Tracker API removes the need for DetectChanges
private static void ChangeCurrentValue()
{
  using (var context = new BreakAwayContext())
  {
    context.Configuration.AutoDetectChangesEnabled = false;

    var hotel = (from d in context.Lodgings
                 where d.Name == "Grand Hotel"
                 select d).Single();

    context.Entry(hotel)
      .CurrentValues["Name"] = "Hotel Pretentious";

    Console.WriteLine("Property Value: {0}", hotel.Name);
    Console.WriteLine("State: {0}", context.Entry(hotel).State);
  }
}

The updated code now disables automatic calling of DetectChanges. The code also prints out the state of the hotel entity, as recorded by the Change Tracker, after the current value for Name has been updated. Go ahead and run the application again:

Property Value: Hotel Pretentious
State: Modified

If we had updated the Name property on the entity, we would expect the state to be Unchanged, since a call to DetectChanges would be required to discover the updated property. However, because the Name property was updated using the Change Tracker API, the state is correctly recorded as Modified without calling DetectChanges.

There may be times when you want to have an editable copy of the current or original values but you don’t want changes to be recorded in the Change Tracker. You’ll see one such scenario when we look at resolving concurrency conflicts later in this chapter. The Clone method will return a copy of any DbPropertyValues instance. Be aware that when you clone current or original values, the resulting copy will not be hooked up to the change tracker. Add the CloneCurrentValues method shown in Example 5-15 to see how cloning works.

Example 5-15. Cloning current values
private static void CloneCurrentValues()
{
  using (var context = new BreakAwayContext())
  {
    var hotel = (from d in context.Lodgings
                  where d.Name == "Grand Hotel"
                  select d).Single();

    var values = context.Entry(hotel).CurrentValues.Clone();

    values["Name"] = "Simple Hotel";

    Console.WriteLine("Property Value: {0}", hotel.Name);
    Console.WriteLine("State: {0}", context.Entry(hotel).State);
  }
}

The code loads the Grand Hotel Lodging from the database and then clones its current values. The value stored for the Name property in the cloned values is updated, and then the value of the Name property in the entity is written out. Update the Main method to call CloneCurrentValues and run the application to see the following output in the console:

Property Value: Grand Hotel
State: Unchanged

As expected, updating the cloned values has no impact on the values or the state of the entity they were cloned from.

Using the SetValues method

In Chapter 4, you learned that you can copy the contents of one DbPropertyValues into another using the SetValues method. For example, you may want users of a client application with access to the change tracker to be able to roll back changes they’ve made to an entity. The easiest way to do this is to copy the original values (when the entity was retrieved from the database) back into the current values. Add the UndoEdits method shown in Example 5-16.

Example 5-16. Copying original values back into current values
private static void UndoEdits()
{
  using (var context = new BreakAwayContext())
  {
    var canyon = (from d in context.Destinations
                  where d.Name == "Grand Canyon"
                  select d).Single();

    canyon.Name = "Bigger & Better Canyon";

    var entry = context.Entry(canyon);
    entry.CurrentValues.SetValues(entry.OriginalValues);
    entry.State = EntityState.Unchanged;

    Console.WriteLine("Name: {0}", canyon.Name);
  }
}

The code retrieves the Grand Canyon Destination from the database and changes its Name. The code then undoes this edit by locating the entry and copying the original values back into the current values. Entity Framework isn’t smart enough to detect that these new values match the original values, so the code also manually swaps the state back to Unchanged. Finally, the name property is printed out to verify that the changes were reverted. Update the Main method to call UndoEdits and run the application. As expected, the changes to the Name property are reverted and displayed in the console like this:

Name: Grand Canyon

Note

SetValues doesn’t just accept DbPropertyValues, but can accept any object. SetValues will attempt to overwrite the property values with the values in the object that’s been passed in. This is done by matching the names of the object’s properties with the names of the DbPropertyValues instance. If a property with the same name is found, the value is copied. If the property on the object is not of the same type as the value stored in the DbPropertyValues, an InvalidOperationException is thrown. Any properties in the object that don’t match the name of a value already stored in the DbPropertyValues are ignored.

Many applications allow you to enter a new record by cloning an existing record. Let’s see how to use SetValues to accomplish this task. You might be a fan of Dave, of Dave’s Dump fame, and want to create a new Dave’s Campsite Lodging using Dave’s Dump as a starting point. Add the CreateDavesCampsite method shown in Example 5-17.

Example 5-17. Copying one entity into another
private static void CreateDavesCampsite()
{
  using (var context = new BreakAwayContext())
  {
    var davesDump = (from d in context.Lodgings
                      where d.Name == "Dave's Dump"
                      select d).Single();

    var clone = new Lodging();
    context.Lodgings.Add(clone);
    context.Entry(clone)
      .CurrentValues
      .SetValues(davesDump);

    clone.Name = "Dave's Camp";
    context.SaveChanges();

    Console.WriteLine("Name: {0}",
      clone.Name);

    Console.WriteLine("Miles: {0}",
      clone.MilesFromNearestAirport);

    Console.WriteLine("Contact Id: {0}",
      clone.PrimaryContactId);
  }
}

The code retrieves Dave’s Dump from the database. Then it creates a new Lodging for Dave’s Campsite and adds it to the context. The current values for the new Campsite are then copied from Dave’s Dump, using the SetValues method. The code then overwrites the name, since we don’t want this new Lodging to have the same name, and saves to the database. Finally, some of the properties of the new Lodging are written out to the console. Update the Main method to call CreateDavesCampsite and run the application:

Name: Dave's Camp
Miles: 32.65
Contact Id: 1

As expected, the displayed values are the same as Dave’s dump, except for the Name property that we overwrote.

Working with Individual Properties

DbPropertyValues is a great way to work with complete sets of values, but there may be times when you just want to work with the change tracking information for one property. You can of course access the values of a single property using DbPropertyValues, but that uses string-based names for the property. Ideally you should be using strongly typed lambda expressions to identify the property, so that you get compile-time checking, IntelliSense, and refactoring support.

You can use the Property, Complex, Reference, and Collection methods on an entry to get access to the change tracking information and operations for an individual property:

  • The Property method is used for scalar and complex properties.

  • The Complex method is used to get additional operations that are specific to complex properties.

  • The Reference and Collection methods are used for navigation properties.

  • There is also a Member method, which can be used for any type of property. The Member method is not strongly typed and only provides access to information that is common to all properties.

Working with Scalar Properties

Let’s start with the Property method. The Property method allows you to read and write the original and current value. It also lets you know whether an individual property is marked as Modified, something that isn’t possible with DbPropertyValues. The WorkingWithPropertyMethod method shown in Example 5-18 will allow you to begin exploring the Property method.

Example 5-18. Accessing change tracking information for a property
private static void WorkingWithPropertyMethod()
{
  using (var context = new BreakAwayContext())
  {
    var davesDump = (from d in context.Lodgings
                      where d.Name == "Dave's Dump"
                      select d).Single();

    var entry = context.Entry(davesDump);

    entry.Property(d => d.Name).CurrentValue =
      "Dave's Bargain Bungalows";

    Console.WriteLine(
      "Current Value: {0}",
      entry.Property(d => d.Name).CurrentValue);

    Console.WriteLine(
      "Original Value: {0}",
      entry.Property(d => d.Name).OriginalValue);

    Console.WriteLine(
      "Modified?: {0}",
      entry.Property(d => d.Name).IsModified);
  }
}

The code retrieves Dave’s Dump from the database and locates the representative entry from the context. It then uses the Property method to change the current value for the Name property. Then the code prints out the current and original values plus the IsModified flag. The IsModified flag tells us if the property is marked as Modified and will be updated when SaveChanges is called. You can update the Main method to call WorkingWithPropertyMethod and run the application to see these results in the console:

Current Value: Dave's Bargain Bungalows
Original Value: Dave's Dump
Modified?: True

The current and original values are displayed as expected, since we changed the value of the Name property. You can see that the property is also marked as modified. There is also a weakly typed overload of Property that accepts a string property name rather than a lambda expression. In fact, all the methods you will see in this section have a string-based overload.

The strongly typed lambda overloads are recommended because they give you a compile-time check, but the string-based overloads can be useful when writing generalized code. For example, you might want to find out which properties are currently marked as Modified. You can get the names of all properties using CurrentValues and then check if they are modified using the Property method. The FindModifiedProperties method shown in Example 5-19 demonstrates this.

Example 5-19. Finding the modified properties of an entity
private static void FindModifiedProperties()
{
  using (var context = new BreakAwayContext())
  {
    var canyon = (from d in context.Destinations
                  where d.Name == "Grand Canyon"
                  select d).Single();

    canyon.Name = "Super-Size Canyon";
    canyon.TravelWarnings = "Bigger than your brain can handle!!!";

    var entry = context.Entry(canyon);
    var propertyNames = entry.CurrentValues.PropertyNames;


IEnumerable<string> modifiedProperties = from name in propertyNames
                             where entry.Property(name).IsModified
                             select name;

    foreach (var propertyName in modifiedProperties)
    {
      Console.WriteLine(propertyName);
    }
  }
}

The code retrieves the Grand Canyon Destination from the database and changes a couple of properties. The code then locates the entry for the Grand Canyon and gets a list of all property names using CurrentValues. Then a LINQ query is used to find which of those property names are marked as modified. The where section of the LINQ query uses the string-based overload of Property to get the change tracking information for the property. Finally, the modified properties are written out to the console. When you run FindModifiedProperties, the names of the two edited properties are written out to the console:

Name
TravelWarnings

The Property method also gives you access to the name of the property and the change tracker entry for the entity containing the property. This information is provided in the Name and EntityEntry properties (Figure 5-1).

EntityEntry and Name for the TravelWarnings property
Figure 5-1. EntityEntry and Name for the TravelWarnings property

In the code you’ve seen so far, we’ve always known this information because we started with the change tracking entry and then the property name to find the information for a property. In Chapters 6 and 7 you will see how the Name and EntityEntry properties are useful in Validation scenarios.

Working with Complex Properties

When working with complex properties, you use the ComplexProperty method to get access to change tracking information and operations. The same operations that you just learned about for scalar properties are all available for complex properties. You can also use the Property method to drill into individual scalar properties on the complex type. The WorkingWithComplexMethod shown in Example 5-20 demonstrates interacting with the properties of the Address complex property in a Person instance.

Example 5-20. Accessing change tracking information for a complex property.
private static void WorkingWithComplexMethod()
{
  using (var context = new BreakAwayContext())
  {
    var julie = (from p in context.People
                  where p.FirstName == "Julie"
                  select p).Single();

    var entry = context.Entry(julie);

    entry.ComplexProperty(p => p.Address)
      .Property(a => a.State)
      .CurrentValue = "VT";

    Console.WriteLine(
      "Address.State Modified?: {0}",
      entry.ComplexProperty(p => p.Address)
        .Property(a => a.State)
        .IsModified);

    Console.WriteLine(
      "Address Modified?: {0}",
      entry.ComplexProperty(p => p.Address).IsModified);

    Console.WriteLine(
      "Info.Height.Units Modified?: {0}",
      entry.ComplexProperty(p => p.Info)
        .ComplexProperty(i => i.Height)
        .Property(h => h.Units)
        .IsModified);
  }
}

The code loads data for Julie from the database and locates the change tracking entry from the context. Next it drills into the Address complex property and then into the scalar State property within Address. The code changes the current value assigned to Address.State and then prints out the IsModified flag for Address.State and for the complex Address property. Finally, the code drills into a nested complex property to check the IsModified flag for Info.Height.Units. You can see that ComplexProperty calls can be chained together to drill into a complex property that is defined in another complex property. Following are the console results after running the WorkingWithComplexMethod:

Address.State Modified?: True
Address Modified?: True
Info.Height.Units Modified?: False

An alternative syntax to access the change tracking information for a complex property is to specify the full path to the property in a single Property call. For example, you could also change the current value of Address.State using the following code:

entry.Property(p => p.Address.State).CurrentValue = "VT";

You can also specify the full path if you are using the string-based overload of Property:

entry.Property("Address.State").CurrentValue = "VT";

When working with complex properties, Entity Framework tracks that state for the complex type, but not for its individual properties. If you check the state of any property within the complex type (for example, the City property of Address), Entity Framework will return the state of the complex type (Address). After changing the Address.State property, every property of Address will be marked as modified.

So far in this section, we have always modified the scalar values of an existing complex type instance. You can also assign a new complex type instance to a complex property. In Example 5-20, instead of editing the State property of the existing Address instance, we could have replaced it with a new instance:

entry.ComplexProperty(p => p.Address)
  .CurrentValue = new Address { State = "VT" };

Replacing the value assigned to a complex property with a new instance will mark the entire complex property as modified.

Note

You may have noticed in Figure 5-1 that a ParentProperty is available after calling Property or ComplexProperty. For properties that are defined directly on an entity, this will always return null. For properties that are defined within a complex property, ParentProperty will return the change tracking information for the parent complex property. For example, if you are looking at the information for the City property inside Address, ParentProperty will give you the information for Address. In the examples in this chapter, we always know the parent property because we started with the entity, then drilled into the complex property, followed by its subproperties.

Working with Navigation Properties

Now it’s time to look at how to access the change tracking information and operations associated with a navigation property. Instead of the Property method, you use the Reference and Collection methods to get to navigation properties:

  • Reference is used when the navigation property is just a reference to a single entity (for example, Lodging.Destination).

  • Collection is used when the navigation property is a collection (for example, Destination.Lodgings).

These methods give you the ability to do several things:

  1. Read and write the current value assigned to the navigation property

  2. Load the related data from the database

  3. Get a query representing the contents of the navigation property

In Explicit Loading, you saw how the Load method can be used to load the contents of a navigation property from the database. You also learned that the IsLoaded flag can be used to determine if the entire contents of a navigation property (for example, Destination.Trips) have already been loaded. In Chapter 2 you also saw how to use the Query method to run a LINQ query against the contents of the navigation property. This was in Querying Contents of a Collection Navigation Property.

Modifying the value of a navigation property

The Reference method gives you access to change tracking information and operations for a navigation property. One piece of information that is available is the value currently assigned to the navigation property. This is accessed via the CurrentValue property. If the navigation property hasn’t been loaded from the database, CurrentValue will return null. You can also set the CurrentValue property to change the entity assigned to the navigation property, therefore changing the relationship. Add the code for WorkingWithReferenceMethod, shown in Example 5-21.

Example 5-21. Change tracking information for a reference navigation property
private static void WorkingWithReferenceMethod()
{
  using (var context = new BreakAwayContext())
  {
    var davesDump = (from d in context.Lodgings
                      where d.Name == "Dave's Dump"
                      select d).Single();

    var entry = context.Entry(davesDump);

    entry.Reference(l => l.Destination)
      .Load();

    var canyon = davesDump.Destination;

    Console.WriteLine(
      "Current Value After Load: {0}",
      entry.Reference(d => d.Destination)
        .CurrentValue
        .Name);

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

    entry.Reference(d => d.Destination)
      .CurrentValue = reef;

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

The code retrieves Dave’s Dump Lodging from the database and locates the change tracking entry from the context. Then it drills into the Destination reference and explicitly loads the related data using Reference().Load(). The name of the Destination that Dave’s Dump is assigned is then written out to the console using the CurrentValue property. Next, we change CurrentValue by assigning the Great Barrier Reef Destination. Finally, we’ll write out the name of the Destination that Dave’s Dump is assigned again, but this time by accessing the navigation property on the davesDump entity itself.

Update the Main method to call WorkingWithReferenceMethod and run the application to see the following results:

Current Value After Load: Grand Canyon
Current Value After Change: Great Barrier Reef

CurrentValue allowed you to read and write the Destination that Dave’s Dump is assigned to. Changing the CurrentValue also updated the navigation property on the davesDump entity.

Modifying navigation properties with the change tracker

Earlier, when working with scalar properties, you saw that DetectChanges was not required when making changes through the change tracker. The same is true for reference navigation properties. Change detection and relationship fix-up occur without DetectChanges being called. To see this in action, update the WorkingWithReferenceMethod method to disable automatic change detection and lazy loading. Add the following code immediately before the LINQ query that retrieves Dave’s Dump from the database:

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.LazyLoadingEnabled = false;

Let’s also print some additional information to the console to observe how the context is tracking the changes. Add the following code after the final Console.WriteLine call in the existing method:

Console.WriteLine(
  "State: {0}",
  entry.State);

Console.WriteLine(
  "Referenced From Current Destination: {0}",
  reef.Lodgings.Contains(davesDump));

Console.WriteLine(
  "Referenced From Former Destination: {0}",
  canyon.Lodgings.Contains(davesDump));

The code prints out the state of Dave’s Dump as recorded by the change tracker. Then it prints out whether Dave’s Dump is present in Lodgings collection on the current Destination (reef) and the former Destination (canyon). Go ahead and run the application again, which will print these results in the console:

After Load: Grand Canyon
After CurrentValue Change: Great Barrier Reef
State: Modified
Referenced From Current Destination: True
Referenced From Former Destination: False

The change tracker is aware that Dave’s Dump is Modified without calling DetectChanges. The change tracker has also taken care of updating the Destination reference on Dave’s Dump, removing Dave’s Dump from the Lodgings collection on the former Destination, and adding it to the new Destination.

Note

More information on relationship fix-up is available in Using DetectChanges to Trigger Relationship Fix-up.

Working with collection navigation properties

You’ve seen many features of working with a reference navigation using the Reference method. The same operations are available when using the Collection method to interact with a collection navigation property. The WorkingWithCollectionMethod method, shown in Example 5-22, runs through some of the same tasks, but this time with a navigation property that points to a collection. We’re using Reservation.Payments as our collection navigation property rather than Destination.Lodgings. Back in Chapter 3, we set up Destination to use a dynamic change tracking proxy so that changes would be automatically reported to the change tracker. But Reservation is not set up to use a change tracking proxy. This will allow us to explore how the Collection method behaves with change detection and relationship fix-up.

Example 5-22. Method to explore interacting with a collection property
private static void WorkingWithCollectionMethod()
{
  using (var context = new BreakAwayContext())
  {
    var res = (from r in context.Reservations
               where r.Trip.Description == "Trip from the database"
               select r).Single();

    var entry = context.Entry(res);

    entry.Collection(r => r.Payments)
      .Load();

    Console.WriteLine(
      "Payments Before Add: {0}",
      entry.Collection(r => r.Payments).CurrentValue.Count);

    var payment = new Payment { Amount = 245 };
    context.Payments.Add(payment);

    entry.Collection(r => r.Payments)
      .CurrentValue
      .Add(payment);

    Console.WriteLine(
      "Payments After Add: {0}",
      entry.Collection(r => r.Payments).CurrentValue.Count);
  }
}

The method loads a Reservation from the database and locates its change tracking entry from the context. Then it drills into the Payments property using the Collection method and, just as we did with the Reference, uses the Load method to explicitly load any related Payments from the database. The method then calls the Collection.CurrentValue.Count property to count how many payments are in the collection and prints out the count. Finally, the method adds a new payment and prints out the count again. Update the Main method to call WorkingWithCollectionMethod and run the application. Here is what you’ll see in the console:

Payments Before Add: 1
Payments After Add: 2

With a Collection, you can use the CurrentValue property to read and write from the relevant collection navigation property (in this case, Payments). CurrentValue on collection navigation properties returns the instance of the collection assigned to the navigation property. In the case of Reservation.Payments, that’s the List<Payment> that gets created in the constructor of Reservation. Therefore, adding or removing from the CurrentValue behaves the same as adding or removing from the navigation property itself. This means that unless the entity is a change tracking proxy, you’ll need DetectChanges to get change detection and relationship fix-up to occur. Let’s see how this affects the results of the WorkingWithCollectionMethod method by adding a line of code to disable automatic change detection. Add the following line of code immediately before the LINQ query that retrieves the Reservation from the database:

context.Configuration.AutoDetectChangesEnabled = false;

Also add the following code after the final Console.WriteLine call. This new code calls DetectChanges after adding the Payment. The value assigned to the foreign key on the new Payment is printed out to the console on either side of the DetectChanges call:

Console.WriteLine(
  "Foreign Key Before DetectChanges: {0}",
  payment.ReservationId);

context.ChangeTracker.DetectChanges();

Console.WriteLine(
  "Foreign Key After DetectChanges: {0}",
  payment.ReservationId);

Go ahead and run the application again. You can see the effect of setting AutoDetectChangesEnabled to false and the explicit DetectChanges call in the console output:

Count Before Add: 1
Count After Add: 2
Foreign Key Before DetectChanges: 0
Foreign Key After DetectChanges: 1

The addition of the Payment to the Payments collection of the Reservation was not automatically detected. Relationship fix-up was not triggered until DetectChanges was called.

Refreshing an Entity from the Database

Throughout this book you have seen how to load data from the database and work with it in-memory. So far, you have only loaded the data one time, but there may be times when you want to refresh or reload a given entity. For example you may have had an entity in memory for a long period of time and want to make sure you have the latest data before displaying it to a user.

Entity Framework includes a Reload method on DbEntityEntry that can be used to refresh an entity with the latest data from the database. The ReloadLodging method shown in Example 5-23 uses this method.

Example 5-23. Reloading an entity from the database
private static void ReloadLodging()
{
  using (var context = new BreakAwayContext())
  {
    var hotel = (from d in context.Lodgings
                  where d.Name == "Grand Hotel"
                  select d).Single();

    context.Database.ExecuteSqlCommand(
      @"UPDATE dbo.Lodgings
        SET Name = 'Le Grand Hotel'
        WHERE Name = 'Grand Hotel'");

    Console.WriteLine(
      "Name Before Reload: {0}",
      hotel.Name);

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

    context.Entry(hotel).Reload();

    Console.WriteLine(
      "Name After Reload: {0}",
      hotel.Name);

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

The method retrieves the Grand Hotel Lodging from the database and then, for the sake of demoing this feature, issues a raw SQL query to update its Name in the database. The code then calls Reload to refresh with the latest data from the database. Notice that the code prints out the value assigned to the Name property and the state of the entity before and after calling Reload. Update the Main method to call ReloadLodging and run the application to see the effect. This is the output in the console:

Name Before Reload: Grand Hotel
State Before Reload: Unchanged
Name After Reload: Le Grand Hotel
State After Reload: Unchanged

You can see that the new value for the Name property was retrieved from the database.

Reload will also overwrite any changes you have in memory. To see this effect, update the ReloadLodging method to edit the Lodging before reloading. Add the following line of code immediately after the LINQ query that populates the hotel variable:

hotel.Name = "A New Name";

The code now modifies the Name property of the entity in memory before calling Reload. This will now be the output of the method:

Name Before Reload: A New Name
State Before Reload: Modified
Name After Reload: Le Grande Hotel
State After Reload: Unchanged

Because we edited the Name property, the entity state is Modified before the Reload. After the Reload, the entity is now marked as Unchanged, because any changes were overwritten with data from the database.

Change Tracking Information and Operations for Multiple Entities

So far you have seen how to get access to the DbEntityEntry for a single entity. Sometimes you might want to get access to entries for all entities or a subset of the entries tracked by the context. You can do this using the DbContext.ChangeTracker.Entries method. There is a generic Entries<TEntity> overload that returns a collection of DbEntityEntry<TEntity> records for all entities that are of the type specified for TEntity. The nongeneric overload of Entries does not allow you to specify the type, and it returns a collection of DbEntityEntry records for all of the tracked entities.

We’ll start by looking at all entries known by the change tracker using the nongeneric overload using the PrintChangeTrackerEntries method shown in Example 5-24.

Example 5-24. Iterating over all entries from the change tracker
private static void PrintChangeTrackerEntries()
{
  using (var context = new BreakAwayContext())
  {
    var res = (from r in context.Reservations
               where r.Trip.Description == "Trip from the database"
               select r).Single();

    context.Entry(res)
      .Collection(r => r.Payments)
      .Load();

    res.Payments.Add(new Payment { Amount = 245 });

    var entries = context.ChangeTracker.Entries();
    foreach (var entry in entries)
    {
      Console.WriteLine(
        "Entity Type: {0}",
        entry.Entity.GetType());

      Console.WriteLine(
        " - State: {0}",
        entry.State);
    }
  }
}

The code retrieves a Reservation from the database and then uses explicit loading to bring its related Payments into memory. The code also creates a new Payment and adds it to the Payments collection of the Reservation. Then we use the Entries method to retrieve all change tracked entries from the context. As the code iterates over the entries, it prints out the type of entity and its current state. Calling PrintChangeTrackerEntries from the Main method results in this console output:

Entity Type: Model.Payment
 - State: Added
Entity Type: Model.Reservation
 - State: Unchanged
Entity Type: Model.Payment
 - State: Unchanged

The Entries method returns an entry for the Reservation and its existing Payment, as well as the new Payment we added. You’ll notice that the entries aren’t returned in the order they began being tracked by the context.

Warning

You shouldn’t rely on the order that entries are returned in, as it may change between versions of Entity Framework.

You can also use LINQ to Objects to query the result of the Entries method. Replace the line of code in PrintChangeTrackerEntries that populates the entries variable to use a LINQ query:

var entries = from e in context.ChangeTracker.Entries()
              where e.State == EntityState.Unchanged
              select e;

This updated code uses a LINQ query to select only the entries for entities that are tracked in the Unchanged state. If you run the application you will see that the information for the Added Payment is no longer displayed:

Entity Type: Model.Reservation
 - State: Unchanged
Entity Type: Model.Payment
 - State: Unchanged

Another way to filter is to use the generic overload of Entries to specify which types you want entries for. Change the Entries call in PrintChangeTrackerEntries again along with the code, which writes to the console:

var entries = context.ChangeTracker.Entries<Payment>();
foreach (var entry in entries)
{
  Console.WriteLine(
    "Amount: {0}",
    entry.Entity.Amount);

  Console.WriteLine(
    " - State: {0}",
    entry.State);
}

The call now uses the generic overload of Entries to specify that we are only interested in entries for the Payment type. Thanks to the generic overload, the Entity property on the returned entries is now strongly typed as Payment. We’ll print out the payment Amount instead of the type of the entity. If you run the application again, you will see that only information for the two Payment entities is printed:

Amount: 245
 - State: Added
Amount: 150.00
 - State: Unchanged

The type that you supply to the generic overload of Entries does not need to be a type that is included in your model. For example, in Chapter 4, you saw the generic overload used to get all entries for entities that implemented a given interface:

context.ChangeTracker.Entries<IObjectWithState>()

Using the Change Tracker API in Application Scenarios

We’ve covered a lot of functionality in this chapter, so let’s look at a couple of examples of how that functionality can be used in an application.

You’ll see how you can use the Change Tracker API to resolve concurrency conflicts and also to log changes that are made during SaveChanges.

Resolving Concurrency Conflicts

A concurrency conflict occurs when you attempt to update a record in the database but another user has updated that same record since you queried for it. By default, Entity Framework will always update the properties that you have modified regardless of whether or not there is a concurrency conflict. However, you can configure your model so that Entity Framework will throw an exception when a concurrency conflict occurs. You do this by specifying that a specific property should be used as a concurrency token.

Note

How to configure your model for optimistic concurrency is covered in detail in Programming Entity Framework, 2e (for EDMX models) and in Programming Entity Framework: Code First (for models defined using Code First).

During SaveChanges Entity Framework will check if the value in the corresponding database column has been updated since the record was bought into memory. A concurrency exception is thrown if the value in the database has changed.

The BAGA model includes two examples of concurrency tokens. The SocialSecurityNumber property on Person is marked with the ConcurrencyCheck attribute. When updating an existing Person, Entity Framework will check that the SSN allocated to the Person when the record was retrieved from the database remains the same in the database. If another user has changed the SSN, SaveChanges will fail and a DbUpdateConcurrencyException will be thrown. The Trip class includes a RowVersion property that is marked with the Timestamp attribute. Timestamp properties are treated the same as other concurrency check properties, except the database will automatically generate a new value for this property whenever any column in the record is updated. This means that when saving changes to an existing Trip, a concurrency exception will be thrown if another user has updated any properties since the Trip was retrieved from the database.

Let’s write a method that will cause a DbUpdateConcurrencyException to be thrown when trying to save a change to an existing Trip. When the exception is thrown, we’ll ask the end user of our application to tell us how they want to resolve the conflict. Go ahead and add the ConcurrencyDemo method shown in Example 5-25.

Example 5-25. Causing a concurrency exception.
private static void ConcurrencyDemo()
{
  using (var context = new BreakAwayContext())
  {
    var trip = (from t in context.Trips.Include(t => t.Destination)
                where t.Description == "Trip from the database"
                select t).Single();

    trip.Description = "Getaway in Vermont";

    context.Database.ExecuteSqlCommand(
      @"UPDATE dbo.Trips
        SET CostUSD = 400
        WHERE Description = 'Trip from the database'");


    SaveWithConcurrencyResolution(context);
  }
}

private static void SaveWithConcurrencyResolution(
  BreakAwayContext context)
{
  try
  {
    context.SaveChanges();
  }
  catch (DbUpdateConcurrencyException ex)
  {
    ResolveConcurrencyConflicts(ex);
    SaveWithConcurrencyResolution(context);
  }
}

The example retrieves an existing Trip from the database and changes its Description property. It then issues a raw SQL query to update the CostUSD column of the same Trip in the database. Executing this statement will cause the database to generate a new value for the RowVersion column in the database. Issuing a raw SQL statement is not a recommended practice and is just used to simulate another user changing data. Next, the code calls the SaveWithConcurrencyResolution helper method. This helper method calls SaveChanges, which will issue an UPDATE command to apply our changes to the Trip. As part of the update process, Entity Framework will check if the RowVersion column in the database still has the same value as it did when we queried for the Trip. Because of change we made with ExecuteSqlCommand, the RowVersion will have changed and a DbUpdateConcurrencyException will be thrown. The example code catches this exception and attempts to resolve the conflict with a custom method, ResolveConcurrencyConflict. Once the conflict is resolved, the code makes a recursive call to SaveWithConcurrencyResolution. The recursive call is used to ensure that we handle any further concurrency conflicts that occur after the first conflict is resolved. Example 5-26 shows the ResolveConcurrencyConflict method.

Example 5-26. Resolving a concurrency conflict
private static void ResolveConcurrencyConflicts(
  DbUpdateConcurrencyException ex)
{
  foreach (var entry in ex.Entries)
  {
    Console.WriteLine(
      "Concurrency conflict found for {0}",
      entry.Entity.GetType());

    Console.WriteLine("
You are trying to save the following values:");
    PrintPropertyValues(entry.CurrentValues);

    Console.WriteLine("
The values before you started editing were:");
    PrintPropertyValues(entry.OriginalValues);

    var databaseValues = entry.GetDatabaseValues();
    Console.WriteLine("
Another user has saved the following values:");
    PrintPropertyValues(databaseValues);

    Console.Write(
      "[S]ave your values, [D]iscard you changes or [M]erge?");

    var action = Console.ReadKey().KeyChar.ToString().ToUpper();
    switch (action)
    {
      case "S":
        entry.OriginalValues.SetValues(databaseValues);
        break;

      case "D":
        entry.Reload();
        break;

      case "M":
        var mergedValues = MergeValues(
          entry.OriginalValues,
          entry.CurrentValues,
          databaseValues);

        entry.OriginalValues.SetValues(databaseValues);
        entry.CurrentValues.SetValues(mergedValues);
        break;

      default:
        throw new ArgumentException("Invalid option");
    }
  }
}

Fortunately, the DbUpdateConcurrencyException gives you access to everything you need to know about the conflict. The exception’s Entries property gives you the DbEntityEntry for each of the entities that had a concurrency conflict.

Note

Because Entity Framework stops at the first exception, the Entries property on DbUpdateConcurrencyException will almost always contain just a single entry. If you have a relationship that does not expose a foreign key property on your entity, Entity Framework treats relationships as separate from the entity; these relationships are known as independent associations. SaveChanges also treats the relationships as separate from the entity. If a concurrency conflict occurs when saving the relationship, the resulting exception will include the entry for the entity on each end of the relationship. This is yet one more good reason to always include foreign key properties in your entities.

The ResolveConcurrencyConflicts method iterates through each of the entries in the exception to resolve the conflict. It lets the user know what type of entity the conflict occurred in by checking the type of the entity in the Entity property. Next the user is shown the current, original, and database values using the PrintPropertyValues method you added back in Example 5-10. The code then gives the user three options for resolving the conflict:

“Save your values”

If the changes that another user has made don’t make sense given the changes the current user is making, the user can proceed with saving his or her values to the database. This option will overwrite any changes that were made by other users, even if those changes were to a property that the user is not trying to update. For example, the drop to $400 that the other user applied might not be applicable now that the Trip is visiting beautiful Vermont.

If the user selects this option, the original values are set to the database values. This updates the RowVersion property with the new value from the database, so that the next save will succeed. Setting the original values will also mark any properties that have a different current value as Modified. In our example, only the Description property was modified. However, the current value for CostUSD is still the value retrieved from the database ($1000), but the database value has been updated ($400). Therefore the CostUSD property will get marked as Modified—to be set back to the value when originally queried ($1000).

“Discard your changes”

If the user’s changes no longer make sense, the user can discard his or her changes and accept the new values that the other user has saved. For example, the user might decide that given the price reduction the other user applied, it’s better to leave the Trip Description as it was.

The DbEntityEntry.Reload method makes this option very simple to implement. Reload will query the database again for the database values. If you wanted to avoid this additional query, you could also set the original and current values to the database values. Remember that Entity Framework isn’t smart enough to move properties back out of the Modified state if you change the current and original value to the same thing. Therefore, you would also need to set the State property on the entry back to Unchanged. Calling Reload has one small advantage; it will always pick up the very latest version of the entity at the time the user decides to discard his or her changes. If another user has modified the affected entity again, after we detected the conflict and queried the database values, then Reload will pick up this latest set of changes.

“Merge”

The user may decide that both sets of changes make sense and they should be merged. The user’s change to the Description may be completely unrelated to the drop in price. The price should remain at $400 but the change to Description should also be applied.

We’ll use a custom MergeValues method, which we are about to add, to calculate the correct values to save. These merged values are then set to the current values. The database values are set to the original values to ensure the RowVersion property has the new value from the database, so that the next save will succeed.

The final piece of code to add is the MergeValues method that will be used when the user decides to merge his or her changes with the changes another user has applied (Example 5-27).

Example 5-27. The MergeValues method
private static DbPropertyValues MergeValues(
  DbPropertyValues original,
  DbPropertyValues current,
  DbPropertyValues database)
{
  var result = original.Clone();

  foreach (var propertyName in original.PropertyNames)
  {
    if (original[propertyName] is DbPropertyValues)
    {
      var mergedComplexValues = MergeValues(
        (DbPropertyValues)original[propertyName],
        (DbPropertyValues)current[propertyName],
        (DbPropertyValues)database[propertyName]);

      ((DbPropertyValues)result[propertyName])
        .SetValues(mergedComplexValues);
    }
    else
    {
      if (!object.Equals(
        current[propertyName],
        original[propertyName]))
      {
        result[propertyName] = current[propertyName];
      }
      else if (!object.Equals(
        database[propertyName],
        original[propertyName]))
      {
        result[propertyName] = database[propertyName];
      }
    }
  }

  return result;
}

MergeValues begins by using the Clone method to create a set of values that will store the merged result. We are cloning from the original values, but you could clone from any of the three sets of values (current, original, or database). Note that the code is going to assume that the three sets of supplied values contain exactly the same properties—in our case, we know this is true because they come from the same entity. The method then loops through each property to perform the merge.

If the property value is a DbPropertyValues, we know it represents a complex type. For complex types, the code uses a recursive call to merge the values in the complex type. The merged values for the complex type are then copied into the result using the SetValues method.

For scalar properties, the code compares the current value to the original value to see if the current user has edited the value. If the current user has edited the value, the current value is copied to the merged result. If the current user hasn’t edited the value but another user has changed it in the database, the database value is copied to the result. If nobody has edited the value, all three value collections agree and the value that was originally cloned can be left in the merged result.

If you want to test out the code, update the Main method to call ConcurrencyDemo and run the application.

Logging During Save Changes

The change tracking information that Entity Framework maintains is also useful if you want to log the changes that a user is making during SaveChanges. We’ll take a look at an example that writes the changes to the Console, but the techniques could be used for any number of logging or auditing solutions.

The logging output could get annoying if we leave it on for every example in this book, so we are going to introduce a flag that allows us to turn it on when we want to use it. Add the following property to your BreakAwayContext class:

public bool LogChangesDuringSave { get; set; }

You’ll need some helper methods to print out the required logging information. Add the two methods shown in Example 5-28 to your BreakAwayContext class. You’ll need to add a using statement for the System, System.Data, and System.Linq namespaces.

Example 5-28. Helper methods for logging
private void PrintPropertyValues(
  DbPropertyValues values,
  IEnumerable<string> propertiesToPrint,
  int indent = 1)
{
  foreach (var propertyName in propertiesToPrint)
  {
    var value = values[propertyName];
    if (value is DbPropertyValues)
    {
      Console.WriteLine(
        "{0}- Complex Property: {1}",
        string.Empty.PadLeft(indent),
        propertyName);

      var complexPropertyValues = (DbPropertyValues)value;
      PrintPropertyValues(
        complexPropertyValues,
        complexPropertyValues.PropertyNames,
        indent + 1);
    }
    else
    {
      Console.WriteLine(
        "{0}- {1}: {2}",
        string.Empty.PadLeft(indent),
        propertyName,
        values[propertyName]);
    }
  }
}

private IEnumerable<string> GetKeyPropertyNames(object entity)
{
  var objectContext = ((IObjectContextAdapter)this).ObjectContext;

  return objectContext
    .ObjectStateManager
    .GetObjectStateEntry(entity)
    .EntityKey
    .EntityKeyValues
    .Select(k => k.Key);
}

The PrintPropertyValues method is almost the same as the PrintPropertyValues you added to the Program class back in Example 5-10. The only difference is that this method accepts the names of the properties that should be printed, rather than printing all properties. As the name suggests, the GetKeyPropertyNames method will give you the names of the properties that make up the key of an entity. There is no way to get this information from the DbContext API, so the code uses the IObjectContextAdapter to get the underlying ObjectContext. You’ll learn more about IObjectContextAdapter in Chapter 8. The code gets the key property names by getting the EntityKey for the entity from the ObjectStateManager. ObjectStateManager is the ObjectContext equivalent to the DbContext.ChangeTracker.

With the helper methods in place, let’s write the logging code. The easiest way to run additional logic during the save process is to override the SaveChanges method on your derived context. The SaveChanges method on DbContext is virtual (Overrideable in Visual Basic) for exactly this purpose. Add the overridden SaveChanges method shown in Example 5-29 to your BreakAwayContext class.

Example 5-29. Overriding SaveChanges to perform logging
public override int SaveChanges()
{
  if (LogChangesDuringSave)
  {
    var entries = from e in this.ChangeTracker.Entries()
                  where e.State != EntityState.Unchanged
                  select e;

    foreach (var entry in entries)
    {
      switch (entry.State)
      {
        case EntityState.Added:
          Console.WriteLine(
            "Adding a {0}",
            entry.Entity.GetType());

          PrintPropertyValues(
            entry.CurrentValues,
            entry.CurrentValues.PropertyNames);
          break;

        case EntityState.Deleted:
          Console.WriteLine(
            "Deleting a {0}",
            entry.Entity.GetType());

          PrintPropertyValues(
              entry.OriginalValues,
              GetKeyPropertyNames(entry.Entity));
          break;

        case EntityState.Modified:
          Console.WriteLine(
            "Modifying a {0}",
            entry.Entity.GetType());

          var modifiedPropertyNames =
            from n in entry.CurrentValues.PropertyNames
            where entry.Property(n).IsModified
            select n;

          PrintPropertyValues(
            entry.CurrentValues,
            GetKeyPropertyNames(entry.Entity)
              .Concat(modifiedPropertyNames));
          break;
      }
    }
  }

  return base.SaveChanges();
}

The code checks if the LogChangesDuringSave property is set to true—by default the property is set to false. If logging is enabled, the logging logic is executed. The code then locates the entries for all entities that are going to be saved—that’s all entities that aren’t in the Unchanged state. For each of these entities, the code identifies the change being performed and the type of entity it is being performed on. Then it prints out the values of some of the properties of the entity. The set of properties that gets printed depends on the type of operation being performed:

  • For Added entities, the entire set of current values that are going to be inserted are printed.

  • For Deleted entities, just the key properties are printed out, since this is enough information to identify the record being deleted.

  • For Modified entities, the key properties and the properties that are being updated are printed out.

If you want to test the code out, add the TestSaveLogging method shown in Example 5-30.

Example 5-30. Method to test out logging during save
private static void TestSaveLogging()
{
  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();

    canyon.TravelWarnings = "Take a hat!";

    context.Lodgings.Remove(canyon.Lodgings.First());

    context.Destinations.Add(new Destination { Name = "Seattle, WA" });

    context.LogChangesDuringSave = true;
    context.SaveChanges();
  }
}

The code retrieves the Grand Canyon Destination and its related Lodgings from the database. The Grand Canyon Destination is then modified, one of its Lodgings is marked for deletion, and a new Destination is added to the context. The code then enables the logging you just added and calls SaveChanges. Update the Main method to call TestSaveLogging and run the application to see the log information in the console:

Adding a new Model.Destination
  - DestinationId: 0
  - Name: Seattle, WA
  - Country:
  - Description:
  - Photo:
  - TravelWarnings:
  - ClimateInfo:
Modifiying an existing System.Data.Entity.DynamicProxies.Destination_C0312EA59B82EAC711175D8C037E196179
A49BDE8FF3F0D6830DDB66725C841B
  - DestinationId: 1
  - TravelWarnings: Take a hat!
Deleting an existing Model.Lodging
  - LodgingId: 1

As expected, logging information is printed out for the three entities that are affected during SaveChanges. You’ll notice that the Lodging we modified has a strange type name—it’s not Model.Destination. This is because we enabled Destination as a change tracking proxy. The type name displayed is the type that Entity Framework creates at runtime that derives from the Model.Destination type.

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

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