2011-04-27 196 views
13

我正在研究mvc3 web应用程序。当用户更新某些内容时,我想将旧数据与用户输入的新数据进行比较,并将每个不同的字段添加到日志中以创建活动日志。发现相同类型的两个实体之间的差异

现在这是我攒动的样子:

[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 
} 

但我真的不想做,对于对象的所有属性。

我不知道什么是迭代通过两个对象来找到不匹配的最佳方式(例如,用户更改名称或状态...),并根据可以存储在另一个差异中的差异构建列表表。

回答

23

这不是说不好,你可以比较“手动”的属性使用反射和写重用的扩展方法 - 你可以以此为出发点:如果你正在使用的EntityFramework你

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; 
    } 
} 
+0

哇,这工作出色!谢谢! – LanFeusT 2011-04-28 00:51:10

+1

非常有用的小片段 - 谢谢! – rwalter 2013-10-22 17:02:50

+0

我另外,如果财产是你需要的proerties转换日期时间: '如果(selfValue为datetime && toValue是DATETIME) { \t \t \t \t \t \t \t \t \t日期时间从=(DateTime的)selfValue; DateTime to =(DateTime)toValue; if(!DateTime.Equals(from,to)) { }' – JoeJoe87577 2015-05-04 13:16:44

0

使用此custom code来比较2个对象。如果他们没有记录它。设计观点在Utility类中创建。

使用它作为object1.IsEqualTo(object2)

+1

有什么问题我的答案。 – Praneeth 2011-04-28 05:19:47

0

实现对你的实体IEquatable<T>。另一种方法是实现自定义IComparer<T>,您可以在其中暴露事件,如果属性不同,将触发事件。

+0

这是我预期的更复杂。我会想象这是一个相当普遍的问题,将有一个简单的解决方案^^我甚至不知道如何实现IEquatable 在我的实体上:/ – LanFeusT 2011-04-27 23:31:23

7

可以直接从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()方法,并将所有审计或活动包含在与最终实体相同的事务中,这意味着在没有更改实体的情况下审计线索将不存在,反之亦然。这保证了一个准确的日志。如果你不能保证审计是准确的,那么它几乎是无用的。

+0

我喜欢这个答案的外观,但是我的代码无法识别ObjectStateManager作为此方法。难道我难住,你觉得呢? – GP24 2013-02-12 06:30:00

+1

我知道是旧的,但是是一个优秀的解决方案,仅为那些没有看到它的两个细节:1-在你需要一个foreach的两块代码之间(var stateChangeEntry在modifiedEntries中)和2-删除“-1” for循环。 – Santiago 2013-09-01 20:06:27

+0

纠正我,如果我错了,但为了这个工作,你需要保持你的上下文打开,这意味着你必须为整个视图模型使用一个上下文(也就是说,如果你想能够检测到多个地方的变化)。只需附加实体也不起作用。在我的情况下,@BrokenGlass解决方案效果更好,因为我将所有的上下文调用包装在使用中。 – Bracher 2015-07-29 08:28:03

1

扩展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; 
     } 

    } 
}