Object Change Tracking via Reflection in .NET

We have this big project we're working on (which I have written about before) and one of the things we need to do on this project is automatic logging of changes made to model objects. I've worked out a way to do this generically, for any object, and I think others might find it just as useful as we have.

Requirements

We needed to be able to compare two objects of the same type, examine their properties, and log which properties have changed value. Further, we needed to be able to mark which property represents the primary key for the instance of the object (which is not expected to change value), and we needed to be able to specify fields whose changes won't be logged. Finally, we wanted to do this generically; for any two objects of the same type, we should be able to compare them and log changes.

In short, we needed this log generator to be:

Able to compare two objects of any type, provided they are the same type as each other

Whenever we instantiate ChangeLogService and call GetChanges, we should get back a List of ChangeLogs that will contain all the changes found between the two objects. But exactly how can we get these changes?

Step 2: Find the Primary Key Property

Now we come to the first real issue we have to solve: we need to record the primary key value for the record that changed.

We can make the assumption that the object will have at least one of those fields represent a primary key value. (In the real world, of course, you could have multiple fields represent the key, but in this tutorial we're assuming that the database has a single-column primary key for all tables). But, even with this assumption, how can we identify which property in the object (i.e. which column in the database) is that primary key?

We will use an attribute:

public class LoggingPrimaryKeyAttribute : Attribute
{
}

We can then use this to decorate the RetailLocation object from earlier:

Step 3: Initialize Change Log Data and Get All Properties

We now need some data about each change log that we're going to create. Specifically, we need the primary key value, the date changed, and the type name. We're using the LoggingPrimaryKeyAttribute to get the first item, and the other two can be gathered from other data, like so:

Notice the call to Attribute.IsDefined. This returns true if the given property (represented by x in this call) has an attribute of the given type defined upon it. This is how we can check if a particular attribute is defined on a particular property (and we'll be using this again in just a bit).

Better still, we now have lists of properties from the two changed objects. We can use these lists to determine which of their properties have changed.

Step 4: Ignore Specified Properties

We have now reached the step where we can compare the properties of the two objects. However, we first need a way to exclude the properties that we don't want to log changes for. To do this, we create another attribute:

public class IgnoreLoggingAttribute : Attribute
{
}

In our sample, we'll pretend that we don't want to record changes made to the Manager's First Name, so we'll decorate RetailLocation like so:

However, if you do this, you'll find that the IF clause is always true. In other words, oldValue will always not be equal to newValue. This is because, in C# the == and != operators perform a reference value check on the objects being compared, which will never be equal (see Jon Skeet's answer on StackOverflow). Instead, what we need to do is change the values to string to directly compare them: