Wednesday, September 1, 2010

Audit/history tracking with EF 4.0

I have spent last two days investigating ways to track change history for an entity. And finally came up with a good idea, which I'd like to share with everyone.
There is a great feature in auto-generated entity code (in the entity data model designer file): support of partial methods.
For each property in an entity there are two partial methods generated:
On[PropertyName]Changed(), On[PropertyName]Changing({property type} value).


This is a great feature wich we are going to use.
Let's see first when to track the changes - what is the best point to keep the history. It's not good idea to make this in the OnXXXChanged method, cause this can be caused a lot, but most of those calls will never affect the entity data. So the best time to do it is when the SaveChanges() method for the Context is called. But how to handle that: The ObjectContext calss, which from our datacontext class is derieved, fires an event named SavingChanges, and also has a partial method, called OnContextCreated.


So first of all lets create a partial class with the same name and within the same namespace as the context class of our entities, and define a partial method named OnContextCreated in it:

public partial class MyModelEntities
{
partial void OnContextCreated()
{

}
}


Now let's create an interface IAuditableEntity, which the entities needing to track the history should implement.


internal interface IAuditableEntity
{
void AuditChanges(MyModelEntities argContext);
}

This interface defines a method, which should be called, when the SaveChanges() method should be called on the Context instance. In our partial context class add the following code:

public partial class MyModelEntities
{
partial void OnContextCreated()
{
this.SavingChanges += new EventHandler(MyModelEntities_SavingChanges);
}

private void MyModelEntities_SavingChanges(object sender, EventArgs e)
{
IEnumerable tmpEntries = this.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Modified);
foreach (ObjectStateEntry tmpEntry in tmpEntries)
{
if (tmpEntry.Entity is IAuditableEntity)
{
(tmpEntry.Entity as IAuditableEntity).AuditChanges(this);
}
}
}
}

This code will make the Context class to call the AuditChanges method of all the entities, implementing the IAuditableEntity interface before updating the datastore with new values. This means, that in the AuditChanges method body in the entity instance you will have control over the actions related to the history tracking. The other side is up to you - how to implement on the data layer the history tracking itself.
Have fun.