This is my first article of 2012 and I thought I had published an article on this previously but apparently not so I will now rectify that oversight.
The first this we need out audit fields, these are added to every entity we need to audit, next we need an Interface to allow us to fine entities with Audit fields.
Figure 1 – Audit Fields
public interface IAuditable
{
String CreatedByUserID { get; set; }
String CreatedDateTime { get; set; }
String UpdatedByUserID { get; set; }
String UpdatedDateTime { get; set; }
}
Listing 1 – IAuditable interface
We then need to add this to each entity that will be audited, this is just applied to you metadata classes on the Partial Class NOT the buddy class.
[MetadataType(typeof(AppAlterMetadata))]
public partial class AppAlter : IAuditable
{
internal class AppAlterMetadata
{
public Object ID { get; set; }
// other field deleted for brevety
// auditing fields
public Object CreatedByUserID { get; set; }
public Object CreatedDateTime { get; set; }
public Object UpdatedByUserID { get; set; }
public Object UpdatedDateTime { get; set; }
}
}
Listing 2 – the interface applied to each entity that requires auditing
Now for the code that does the auditing automatically,
public partial class MyEntities
{
/// <summary>
/// Called when [context created].
/// </summary>
partial void OnContextCreated()
{
// Register the handler for the SavingChanges event.
this.SavingChanges += new EventHandler(context_SavingChanges);
}
/// <summary>
/// Handles the SavingChanges event of the context control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private static void context_SavingChanges(object sender, EventArgs e)
{
var objects = ((ObjectContext)sender).ObjectStateManager;
// handle auditing
AuditingHelperUtility.ProcessAuditFields(objects.GetObjectStateEntries(EntityState.Added));
AuditingHelperUtility.ProcessAuditFields(objects.GetObjectStateEntries(EntityState.Modified), InsertMode: false);
}
/// <summary>
/// Auditing helper utility class
/// </summary>
internal static class AuditingHelperUtility
{
internal static void ProcessAuditFields(IEnumerable<Object> list, bool InsertMode = true)
{
foreach (ObjectStateEntry item in list)
{
var appUserID = GetUserId();
// deal with insert and update entities
var auditEntity = item.Entity as IAuditable;
if (auditEntity != null)
{
if (InsertMode)
{
auditEntity.CreatedByUserID = appUserID;
auditEntity.CreatedDateTime = DateTime.Now;
}
auditEntity.UpdatedByUserID = appUserID;
auditEntity.UpdatedDateTime = DateTime.Now;
}
}
}
}
public static String GetUserId()
{
return System.Web.HttpContext.Current.User.Identity.Name;
}
}
Listing 3 – the Audit code
Lets break this down into three sections
Section 1
Here we wire-up the SavingChanges handler in the OnContextCreated() partial method to do this we first need to create a partial class from out entities for you look in the EDMX code behind file you will see something like this;
Figure 2 – Entities classes
so we add a new class to the the project, make sure it has the same namespace as the EDMX code behind file (this is pretty much the same as for out metadata classes) and then we add the partial class same as the MyEntities (this will be the name you gave it when creating but it is there in the code behind you can’t miss it) class, see Listing 3.
The method is wired up with this line of code:
this.SavingChanges += new EventHandler(context_SavingChanges);
Section 2
Now in the context_SavingChanges method we simply get the ObjectStateManager which has all the objects that are being added, updated and deleted, here we are only interested in the Added and Modified items. All we do is call our helper with each collection of objects.
Section 3
Looking at Listing 3 you will see the AuditingHelperUtility and it’s ProcessAuditFields method, here we first of all cast the each entity to the IAuditable interface and check for null if it isn't then set the appropriate properties and exit.
Finally
This can be expanded to cover many different requirements, I have maintained a separate audit table using this method with the addition of a little reflection.