views:

48

answers:

2

I have certain entities that have audit trail elements like LastUpdated and LastUpdatedBy. I am looking to determine the most appropriate way to effect an update of these fields to the current date and time and current user when a model is changed.

The simplest implementation would be to affect this change in my controller before I update the objectcontext but this would result in lots of repeated code. I could refactor this a bit and possibly create a helper method that I could call and pass the model too thus reducing things to a single line of code in my controller but I honestly would rather this be automatic, something the controller code didnt have to worry about.

Has anyone approached this problem before? What was your solution? What do you suggest?

(ps mvc and ef noob so there might be a framework type option I am missing)

A: 

I have done stuff like this, i used T4 templates. See: Entity Framework Trigger Like Auditing

Basically I just made all entities extend an interface, and then in SaveChanges iterate through all that are "IAuditable" and set the update/insert dates.

Worked well for us, but we also went through and for every entity made sure the audit fields had the same names. Once you do that it is easy to generate the custom code automatically for those tables that contain the audit field. Thank make sense?

Nix
+2  A: 

I followed a similar pattern as @Nix, only I didn't use T4 since I prefer code-first development instead of the designer. If you have a Context called MyDbContext, you can partial class it to override the SaveChanges() method. I also have an IAuditable class that all my necessary entities extend from.

public interface IAuditable
{
    User CreatedBy { get; set; }
    DateTime? DateCreated { get; set; }
    User ModifiedBy { get; set; }
    DateTime? DateModified { get; set; }
    User DeletedBy { get; set; }
    DateTime? DateDeleted { get; set; }
}

// Classes that implement the IAuditable here.

public class User : EntityObject
{
    public string Username { get; set; }
    public string DisplayName { get; set; }
    // etc...
}

partial class MyDbModel
{
    public override int SaveChanges(SaveOptions options)
    {

        // Search all added, modified or deleted entities.
        var entries = this.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified | EntityState.Deleted);

        foreach (var entry in entries)
        {

            var auditable = entry.Entity as IAuditable;

            if (auditable != null)
            {
                if (entry.State == EntityState.Added)
                {
                    auditable.CreatedBy = null; // TODO - Get current user.
                    auditable.DateCreated = DateTime.Now;
                    auditable.ModifiedBy = null; // TODO - Same as CreatedBy?
                    auditable.DateModified = DateTime.Now;
                }
                else if (entry.State == EntityState.Modified)
                {
                    auditable.ModifiedBy = null; // TODO - Get current user.
                    auditable.DateModified = DateTime.Now;
                }
                else if (entry.State == EntityState.Deleted)
                {
                    auditable.DeletedBy = null; // TODO - Get current user.
                    auditable.DateDeleted = DateTime.Now;
                }
            }
        }


        return base.SaveChanges(options);
    }
}
TheCloudlessSky
I was going to post the same code except I prefer reflection vs implementing an interface.
jfar
@jfar - The only reason I wouldn't go the reflection route is the performance impact.
TheCloudlessSky
@TheCloudlessSky - The performance impact is negligible. Reflection is not that slow. 1000 iterations test: Reflection: 12 ms, Native: 2ms. Not a big deal. I'd estimate a large unit of work would update 10 entities at a time, 1ms for reflection, 0ms for native. I'm sure your users won't care about 1ms.
jfar
@jfar - It really depends on the situation with Reflection. I created an IoC container where using raw `Activator.CreateInstance` is *way* slower than `ILGenerator.Emit` to create an instance of a type. For this situation, I handle a lot of requests per second, so to me ever ms counts. Also I think using the interface makes things clearer when someone else is reviewing the code. Again, it really depends on the situation.
TheCloudlessSky