Найти различия между двумя объектами того же типа
Я работаю над веб-приложением mvc3. Когда пользователь что-то обновляет, я хочу сравнить старые данные с новым, вводимым пользователем, и для каждого отдельного поля добавить их в журнал для создания журнала активности.
Сейчас это то, что выглядит мое действие save:
[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);
}
Я обнаружил, что могу использовать это для итерации через мое свойство oldCompLang:
var oldpropertyInfos = oldCompLang.GetType().GetProperties();
Но это действительно не помогает, поскольку оно показывает мне только свойства (Id, Name, Status...), а не значения этих свойств (1, Hello, Ready...).
Я мог бы пойти тяжело:
if (oldCompLang.Status != newcomplang.Status)
{
// Add to my activity log table something for this scenario
}
Но я действительно не хочу делать это для всех свойств объекта.
Я не уверен, что лучший способ итерации через оба объекта найти несоответствия (например, пользователь изменил имя или статус...) и создать список из тех различий, которые я могу сохранить в другой таблице.
Ответы
Ответ 1
Это не так уж плохо, вы можете сравнить свойства "вручную" с помощью отражения и написать методы расширения для повторного использования - вы можете принять это как отправную точку:
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;
}
}
Ответ 2
Если вы используете EntityFramework, вы можете получать изменения непосредственно из ObjectContext
Получение записей изменений состояния:
var modifiedEntries= this.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
Получение сохраненных имен свойств и исходных и измененных значений:
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();
}
Это лучше, чем ответ @BrokenGlass, потому что это пойдет глубоко в графе объектов для любых измененных состояний и даст вам измененные свойства связанных коллекций. Это также лучше, потому что это отражает все, что ObjectContext в конечном итоге сохранит в базе данных. С принятым решением вы можете получить изменения свойств, которые на самом деле не будут сохраняться в ситуации, когда вы отключитесь через контекст объекта.
С EF 4.0 вы также можете переопределить метод SaveChanges() и перенести любой аудит или активность в ту же транзакцию, что и сохранение в конечном итоге сущности, что означает, что контрольный журнал не будет существовать без изменений сущностей и наоборот. Это гарантирует точный журнал. Если вы не можете гарантировать, что аудит является точным, чем его почти бесполезный.
Ответ 3
Расширение jfar-ответа, чтобы дать некоторые пояснения и изменения, которые я должен был сделать, чтобы заставить его работать:
// 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;
}
}
}
Ответ 4
используйте этот настраиваемый код для сравнения 2 объектов. Если они не смогут зарегистрировать его. Дизайн точки зрения Создайте это в классе Utility.
используйте его как object1.IsEqualTo(object2)
Ответ 5
Внедрите IEquatable<T>
в свой объект. Другой способ - реализовать пользовательский IComparer<T>
, где вы можете, например, выставить событие, которое будет запущено, если свойство отличается.