Quick & dirty object change tracking in C#
I ran into a issue the other day that was fairly straight forward to solve. I needed to see all of the properties that changed on a object since it was originally setup. The initial state of the object can be anything, either a freshly instanced object, a deserialized object or a object created by setting properties, one at a time during start up or something.
What I wanted to do was essentially perform a SQL update, that just updated the properties that were changed when the user presses a save button. So by creating a Initialize()
method on my objects (or within a Load method or constructor, doesn't matter) I save a dictionary of the current objects property values like such:
public class MyModel
{
private Dictionary<string, object> mOriginalValues = new Dictionary<string, object>();
public void Initialize()
{
PropertyInfo[] properties = this.GetType().GetProperties();
// Save the current value of the properties to our dictionary.
foreach (PropertyInfo property in properties)
{
this.mOriginalValues.Add(property.Name, property.GetValue(this));
}
}
}
Pretty easy right? We just use a bit of reflection to grab the current state of the object and store it. Next, later on during the applications run-time when the user presses save, we essentially grab all of the current properties again. This time however, we compare the current properties to the previous ones and return a Dictionary of the properties that changed.
public Dictionary<string, object> GetChanges()
{
PropertyInfo[] properties = this.GetType().GetProperties();
var latestChanges = new Dictionary<string, object>();
// Save the current value of the properties to our dictionary.
foreach (PropertyInfo property in properties)
{
latestChanges.Add(property.Name, property.GetValue(this));
}
// Get all properties
PropertyInfo[] tempProperties = model.GetType().GetProperties().ToArray();
// Filter properties by only getting what has changed
properties = tempProperties.Where(p => !Equals(p.GetValue(model, null), model.OrginalValues[p.Name])).ToArray();
foreach (PropertyInfo property in properties)
{
latestChanges.Add(property.Name, property.GetValue(this));
}
return latestChanges;
}
If you wanted to, you could either re-initialize the model prior to returning the latest changes, or in the calling method, re-invoke the initialize method to set the models original state to the current state. Essentially, finalizing all of the changes and allowing you to track additional changes once again.
Within your app, you can use if (this.myModel.GetChanges().Count == 0)
to determine if the model has changed at all, and therefore provide feedback to the user if they need to save or not. You can use this to compose update statements in SQL so you only update the fields you need, or use this to minimize the amount of data being sent over the cellular data in a Windows Phone 8 app.
This is the complete code I put into a abstract parent class that my objects will inherit from.
public abstract class MyBaseClass
{
private Dictionary<string, object> mOriginalValues = new Dictionary<string, object>();
public void Initialize()
{
PropertyInfo[] properties = this.GetType().GetProperties();
// Save the current value of the properties to our dictionary.
foreach (PropertyInfo property in properties)
{
this.mOriginalValues.Add(property.Name, property.GetValue(this));
}
}
public Dictionary<string, object> GetChanges()
{
PropertyInfo[] properties = this.GetType().GetProperties();
var latestChanges = new Dictionary<string, object>();
// Save the current value of the properties to our dictionary.
foreach (PropertyInfo property in properties)
{
latestChanges.Add(property.Name, property.GetValue(this));
}
// Get all properties
PropertyInfo[] tempProperties = model.GetType().GetProperties().ToArray();
// Filter properties by only getting what has changed
properties = tempProperties.Where(p => !Equals(p.GetValue(model, null), model.OrginalValues[p.Name])).ToArray();
foreach (PropertyInfo property in properties)
{
latestChanges.Add(property.Name, property.GetValue(this));
}
return latestChanges;
}
}