Implementing Audit Log / Change History with MVC & Entity Framework

asp.net-mvc, audit, c#, entity-framework

Solution

IF you are using EF 4 you can subscribe to the `SavingChanges` event.

Since `Entities` is a partial class you can add additional functionality in a separate file. So create a new file named `Entities` and there implement the partial method `OnContextCreated` to hook up the event

public partial class Entities
{
    partial void OnContextCreated()
    {
        SavingChanges += OnSavingChanges;
    }

    void OnSavingChanges(object sender, EventArgs e)
    {

        var modifiedEntities = ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
        foreach (var entry in modifiedEntities)
        {
            var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
            var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
            foreach (var propName in modifiedProps)
            {
                var newValue = currentValues[propName];
                //log changes
            }
        }
    }
}

If you are using EF 4.1 you can go through this article to extract changes

Problem

I am building in a Change History / Audit Log to my MVC app which is using the Entity Framework. So specifically in the edit method `public ActionResult Edit(ViewModel vm)`, we find the object we are trying to update, and then use `TryUpdateModel(object)` to transpose the values from the form on to the object that we are trying to update. I want to log a change when any field of that object changes. So basically what I need is a copy of the object before it is edited and then compare it after the `TryUpdateModel(object)` has done its work. i.e. ``` [HttpPost] public ActionResult Edit(ViewModel vm) { //Need to take the copy here var object = EntityFramework.Object.Single(x=>x.ID = vm.ID); if (ModelState.IsValid) { //Form the un edited view model var uneditedVM = BuildViewModel(vm.ID); //this line seems to confuse the EntityFramework (BuildViewModel() is used to build the model when originally displaying the form) //Compare with old view model WriteChanges(uneditedVM, vm); ... TryUpdateModel(object); } ... } ``` But the problem is when the code retrieves the "unedited vm", this is causing some unexpected changes in the EntityFramework - so that `TryUpdateModel(object);` throws an `UpdateException`. So the question is - in this situation - how do I create a copy of the `object` outside of EntityFramework to compare for change/audit history, so that it does not affect or change the EntityFramework at all edit: Do not want to use triggers. Need to log the username who did it. edit1: Using EFv4, not too sure how to go about overriding `SaveChanges()` but it may be an option This route seems to be going nowhere, for such a simple requirement! I finally got it to override properly, but now I get an exception with that code: ``` public partial class Entities { public override int SaveChanges(SaveOptions options) { DetectChanges(); var modifiedEntities = ObjectStateManager.GetObjectStateEntries(EntityState.Modified); foreach (var entry in modifiedEntities) { var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry).GetModifiedProperties(); //This line throws exception The ObjectStateManager does not contain an ObjectStateEntry with a reference to an object of type 'System.Data.Objects.EntityEntry'. var currentValues = ObjectStateManager.GetObjectStateEntry(entry).CurrentValues; foreach (var propName in modifiedProps) { var newValue = currentValues[propName]; //log changes } } //return base.SaveChanges(); return base.SaveChanges(options); } } ```

Original source