The official source of information on Managed Providers, DataSet & Entity Framework from Microsoft
I’ve been looking through the Entity Framework MSDN forums and one useful tip that I wanted to blog about was how to customize which original values are tracked so that you can optimize the service payload and to customize the number of things in the database UPDATE SQL. Self-tracking entities track at least some original values to support optimistic concurrency. However, there are scenarios where you may want to customize whether your self-tracking entities to track original values for all properties, just the concurrency tokens (such as a rowversion), or not to no original values at all. Additionally, the Entity Framework has the capability to adjust the UPDATE SQL statement that is issued to the database to include all properties or only those that actually changed. In this post I’ll describe the default self-tracking entity behavior and how to customize the template to use some of these other options.
For simplicity, in this post I am using a single table called “Category” which I defined in SQL Server with an “Id” primary key, strings for “Name” and “Description”, and a timestamp column called “LastChanged” which I use as a concurrency token:
Out of the box, the self-tracking entity template tracks original values for “required original values” inside the ChangeTracker.OriginalValues dictionary. There really aren’t “required original values”, but there are properties that give you more fidelity and a stronger guarantee that your update is going to do what you expected it to do. When the self-tracking entity template points at an edmx file, the following values are considered “required original values” by default:
· Primary key properties
· Foreign key properties
· Properties marked with a ConcurrencyModeof “Fixed”
· If the entity is mapped to a stored procedure for an update, then all properties are “required original values” (because the sproc allows passing in all current values and all original values)
When one of these “required original value” properties is changed, the original value is stored in the ChangeTracker.OriginalValues dictionary and the entity is marked as “Modified”. The ApplyChanges extension method that is part of the context self-tracking entity template is responsible for taking this information and setting it properly in the ObjectContext’s state manager so that the change can be saved to the database.
ApplyChanges will first see that the entity is marked as Modified and will call the ObjectStateManager.ChangeObjectState to make the modification. ChangeObjectState will mark the entity as Modified and will mark every property as being modified. The reason for this is that the self-tracking entity does not know which non-“required original values” changed, so it just sends them all to be safe. As a result, even if only the Category’s “Name” property was changed, the UPDATE SQL statement that is sent to the database looks like this with both “Name” and “Description” in the set clause:
set [Name] = @0, [Description] = @1
where (([Id] = @2) and ([LastChanged] = @3))
So by default, self-tracking entities trade a reduced client-service communication size (by sending fewer original values) for a more complicated UPDATE SQL statement.
The first common customization request is about how to record original values for all properties. This can be done with a small customization to the Model.tt self-tracking entity template: modify the Model.tt file on line 15 to pass “false” into the constructor for OriginalValueMembers:
15: OriginalValueMembers originalValueMembers = new
OriginalValueMembers(allMetadataLoaded, metadataWorkspace, ef);
15: OriginalValueMembers originalValueMembers = new
OriginalValueMembers(false, metadataWorkspace, ef);
The OriginalValueMembers class determines which properties are “required original values”. By passing in “false”, you are telling the class that all properties should be “required original values”. Now when you modify the “Name” property of a Category, you will see its original value stored in the ChangeTracker.OriginalValues dictionary:
using (var ctx = new PlaygroundEntities())
c = ctx.Categories.First();
c.Name = "NewName";
Debug.Assert(ObjectState.Modified == c.ChangeTracker.State);
Because all properties will now record an original value when they are changed, the size of the message between a client and service will increase over the default. However, storing all the original values gives you the opportunity to do some pretty interesting things such as to update only modified properties (see below), or possibly implement a “RejectChanges” method that reverts an entity back to its original values (let me know if you want to know more about this and I can do another post).
The next change we’ll make to the self-tracking entity template is how to simplify the UPDATE SQL that is sent to the database to include only properties that were actually modified. There are two good ways to do this:
1. Modify ApplyChanges to use the fact that original values are tracked for every property to change the entity to the “Modified” state rather than using ChangeObjectState which marks all properties as modified. This is the simplest approach.
2. Modify the ObjectChangeTracker to record only the fact that a property changed, and modify ApplyChanges to mark only those properties as modified. This is more complicated than the first option, but as the additional advantage of not requiring original values for every property and so reduces the client to service message size. There's an updated Context Template and Model template attached at the bottom of this post if you want to skip the details.
The first option only requires another single line update on top of the “Tracking All Original Values” change that was made in the above section. In the Model.Context.tt file, change the ChangeEntityStateBasedOnObjectState method on line 927 that uses ChangeObjectState to simply put the entity into the Unchanged state rather than the Modified state:
927: context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
927: context.ObjectStateManager.ChangeObjectState(entity, EntityState.Unchanged);
ApplyChanges initially puts all entities in the Added state. By putting the entity in the Unchanged state rather than the Modified state, this allows the transition from Unchanged to Modified to happen when original values are set later in ApplyChanges. As a result, if only the category’s “Name” is modified, this will be the only property in the set clause of the UPDATE SQL:
set [Name] = @0
where (([Id] = @1) and ([LastChanged] = @2))
This approach is nice because it is fairly simple to implement (only two one-line template changes), but it does come with the overhead of sending around original values for all modified properties, which as I mentioned before causes the message size between client and server to be larger.
If you want the best of both worlds with a small message size and a small update size, you can use Option 2 above where the ObjectChangeTracker is modified to record the fact that specific properties are modified, but the original value isn’t stored.
The first step to do this is to add a collection of the changed property names to the ObjectChangeTracker class (in this example I’ll just use a collection of strings to record this, but you could further optimize this by coming up with a scheme where you used a BitArray to record changed properties). First we’ll add a public field called ChangedProperties to the ObjectChangeTracker class that is in the Model.tt file:
1437: public HashSet<string> ChangedProperties = new HashSet<string>();
Let’s also add a method to the ObjectChangeTracker called RecordPropertyChange to make it easy to use this field:
1572: internal void RecordPropertyChange(string propertyName)
1574: if (_changeTrackingEnabled && _objectState != ObjectState.Added)
1576: if (!ChangedProperties.Contains(propertyName))
Next, we’ll need to change the entity classes to use our new method to record that a scalar non-“required original value” property has changed:
And we’ll need to do the same for complex types:
We’ll also need to update the AcceptChanges method to clear this set of properties:
1562: public void AcceptChanges()
1568: ChangeTrackingEnabled = true;
1569: _objectState = ObjectState.Unchanged;
Now entities will record all specific property changes, even though an original value is not always stored. We can then use the ObjectChangeTracker.ChangedProperties collection in the Model.Context.tt file to update ApplyChanges to mark these specific properties as modified. The first change to ApplyChanges will be to make the change to stop using ChangeObjectState for Modified entities that is described in Option 1 in this section. Next, we’ll update the UpdateOriginalValues method in two places, first to make sure we do something when we see a ChangedProperty:
806: if (entity.ChangeTracker.State == ObjectState.Unchanged ||
807: entity.ChangeTracker.State == ObjectState.Added ||
808: (entity.ChangeTracker.OriginalValues == null &&
809: entity.ChangeTracker.ChangedProperties.Count == 0))
811: // nothing to do here
And second, to actually use the values in ChangedProperties to mark a property as modified:
830: if(property.TypeUsage.EdmType is PrimitiveType &&
entity.ChangeTracker.OriginalValues.TryGetValue(property.Name, out value))
832: originalValueRecord.SetValue(property, value);
830: if (property.TypeUsage.EdmType is PrimitiveType)
833: if (entity.ChangeTracker.OriginalValues.TryGetValue(property.Name, out value))
835: originalValueRecord.SetValue(property, value);
837: else if(entity.ChangeTracker.ChangedProperties.Contains(property.Name))
Now you are ready to use your template to minimize what is in the message between client and server, and to minimize what is included in your SQL UPDATE.
Self-tracking entities can be customized to store either all original values, only the “required original values” (the default), or no original values with a few changes to the model and context templates. You can also further customize your templates to change the UPDATE SQL that is sent to the database to tailor how your application uses optimistic concurrency. If you have any questions about self-tracking entities or want to see explanations of additional patterns, just let me know.
Entity Framework Team