I'm working on an mvc3 web app. When the user updates something, I want to compare the old data to the new one the user is inputing and for each field that is different add those to a log to create an activity log.
Right now this is what my save action looks like:
[HttpPost]
public RedirectToRouteResult SaveSingleEdit(CompLang newcomplang)
{
var oldCompLang = _db.CompLangs.First(x => x.Id == newcomplang.Id);
_db.CompLangs.Attach(oldCompLang);
newcomplang.LastUpdate = DateTime.Today;
_db.CompLangs.ApplyCurrentValues(newcomplang);
_db.SaveChanges();
var comp = _db.CompLangs.First(x => x.Id == newcomplang.Id);
return RedirectToAction("ViewSingleEdit", comp);
}
I found that I could use this to iterate through my property of oldCompLang:
var oldpropertyInfos = oldCompLang.GetType().GetProperties();
But this doesn't really help as it only shows me the properties (Id, Name, Status...) and not the values of these properties (1, Hello, Ready...).
I could just go the hard way:
if (oldCompLang.Status != newcomplang.Status)
{
// Add to my activity log table something for this scenario
}
But I really don't want to be doing that for all the properties of the object.
I'm not sure what's the best way to iterate through both objects to find mismatches (for example the user changed the name, or the status...) and build a list from those differences that I can store in another table.
It's not that bad, you can compare the properties "by hand" using reflection and write an extension methods for reuse - you can take this as a starting point:
public static class MyExtensions
{
public static IEnumerable<string> EnumeratePropertyDifferences<T>(this T obj1, T obj2)
{
PropertyInfo[] properties = typeof(T).GetProperties();
List<string> changes = new List<string>();
foreach (PropertyInfo pi in properties)
{
object value1 = typeof(T).GetProperty(pi.Name).GetValue(obj1, null);
object value2 = typeof(T).GetProperty(pi.Name).GetValue(obj2, null);
if (value1 != value2 && (value1 == null || !value1.Equals(value2)))
{
changes.Add(string.Format("Property {0} changed from {1} to {2}", pi.Name, value1, value2));
}
}
return changes;
}
}
If you are using EntityFramework you can get changes directly from the ObjectContext
Getting the state change entries:
var modifiedEntries= this.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
Getting saved property names and original and changed values:
for (int i = 0; i < stateChangeEntry.CurrentValues.FieldCount - 1; i++)
{
var fieldName = stateChangeEntry.OriginalValues.GetName(i);
if (fieldName != changedPropertyName)
continue;
var originalValue = stateChangeEntry.OriginalValues.GetValue(i).ToString();
var changedValue = stateChangeEntry.CurrentValues.GetValue(i).ToString();
}
This is better than @BrokenGlass's answer because this will go deep in the object graph for any changed states and will give you the changed properties of associated collections. It is also better because this reflects everything the ObjectContext will eventually save to the database. With the accepted solution you may get property changes that won't actually be persisted in the situation where you become disconnected via the object context.
With EF 4.0 you can also override the SaveChanges() method and wrap any auditing or activity in the same transaction as the eventual entity save meaning an audit trail won't exist without the entities being changed and vice versa. This guarantees an accurate log. If you can't guarantee an audit is accurate than its almost useless.
Extending jfar's answer to provide some clarification and changes that I had to make to get it to work:
// Had to add this:
db.ChangeTracker.DetectChanges();
// Had to add using System.Data.Entity.Infrastructure; for this:
var modifiedEntries = ((IObjectContextAdapter)db).ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var stateChangeEntry in modifiedEntries)
{
for (int i = 0; i < stateChangeEntry.CurrentValues.FieldCount; i++)
{
var fieldName = stateChangeEntry.OriginalValues.GetName(i);
var changedPropertyName = stateChangeEntry.CurrentValues.GetName(i);
if (fieldName != changedPropertyName)
continue;
var originalValue = stateChangeEntry.OriginalValues.GetValue(i).ToString();
var changedValue = stateChangeEntry.CurrentValues.GetValue(i).ToString();
if (originalValue != changedValue)
{
// do stuff
var foo = originalValue;
var bar = changedValue;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With