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); } } ```