2011-04-27 17 views
13

Estoy trabajando en una aplicación web mvc3. Cuando el usuario actualiza algo, quiero comparar los datos anteriores con el nuevo que el usuario ingresa y para cada campo que es diferente, agréguelos a un registro para crear un registro de actividades.Encontrar las diferencias entre dos entidades del mismo tipo

En este momento esto es lo que mi acción de guardar se parece a:

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

me di cuenta que podía usar esto para iterar a través de mi propiedad de oldCompLang:

var oldpropertyInfos = oldCompLang.GetType().GetProperties(); 

Pero esto no hace realmente ayuda, ya que solo me muestra las propiedades (Id, Name, Status ...) y no los valores de estas propiedades (1, Hello, Ready ...).

tan sólo pudiera ir por el camino difícil:

if (oldCompLang.Status != newcomplang.Status) 
{ 
    // Add to my activity log table something for this scenario 
} 

Pero realmente no quiero estar haciendo que para todas las propiedades del objeto.

No estoy seguro de cuál es la mejor manera de recorrer ambos objetos para encontrar discrepancias (por ejemplo, el usuario cambió el nombre o el estado ...) y crear una lista a partir de esas diferencias que puedo almacenar en otro mesa.

Respuesta

23

no es tan malo, usted puede comparar las propiedades "a mano" por medio de la reflexión y escribir un métodos de extensión para su reutilización - puede tomar esto como un punto de partida:

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

¡Guau, eso funciona de maravilla! ¡Gracias! – LanFeusT

+1

Fragmento muy útil: ¡gracias! Mi – rwalter

+0

Además, si la propiedad es una fecha y hora que usted necesita para convertir los proerties: 'si (selfValue es DateTime && toValue es DateTime) { \t \t \t \t \t \t \t \t \t DateTime de selfValue = (DateTime); DateTime to = (DateTime) toValue; if (! DateTime.Equals (from, to)) { } ' – JoeJoe87577

0

utilice este custom code para comparar 2 objetos. Si no lo logran, inicie sesión. Diseño punto de vista Crear esto en la clase de utilidad.

uso como object1.IsEqualTo(object2)

+1

Lo que está mal con mi respuesta. – Praneeth

0

Implementar IEquatable<T> en su entidad. Otra forma es implementar IComparer<T> personalizado donde puede, por ejemplo, exponer el evento que se activará si la propiedad es diferente.

+0

Esto es mucho más complicado de lo que esperaba. Me hubiera imaginado que este es un problema bastante común que tendría una solución simple ^^ Ni siquiera estoy seguro de cómo implementar ese IEquatable en mi entidad:/ – LanFeusT

7

Si está utilizando ADO.NET Entity Framework se puede conseguir cambios directamente desde el ObjectContext

conseguir las entradas de cambio de estado:

var modifiedEntries= this.ObjectStateManager.GetObjectStateEntries(EntityState.Modified); 

Obtención de nombres de propiedades salvado y valores originales y modificados :

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

Esto es mejor que la respuesta de @ BrokenGlass porque esto profundizar en el gráfico de objetos de cualquier Unidos cambió y le dará las propiedades modificadas de colecciones asociadas. También es mejor porque esto refleja todo lo que ObjectContext eventualmente guardará en la base de datos. Con la solución aceptada, puede obtener cambios en la propiedad que no se mantendrán en la situación en la que se desconectará a través del contexto del objeto.

Con EF 4.0 también puede anular el método SaveChanges() y ajustar cualquier auditoría o actividad en la misma transacción que la entidad event save, lo que significa que no existirá un seguimiento de auditoría sin las entidades modificadas y viceversa. Esto garantiza un registro preciso. Si no puede garantizar que una auditoría sea precisa, es casi inútil.

+0

Me gusta el aspecto de esta respuesta, pero mi código no reconocerá el ObjectStateManager como un método para esto. Me ha dejado perplejo, ¿qué piensas? – GP24

+1

Sé que es viejo pero es una excelente solución, dos detalles solo para aquellos que no lo vieron: 1- entre los 2 bloques de código necesitas un foreach (var stateChangeEntry en modifiedEntries) y 2- eliminar el "-1" en el bucle for. – Santiago

+0

Corrígeme si me equivoco pero, para que esto funcione, debes mantener tu contexto abierto, lo que significa que debes usar un contexto para todo tu modelo de vista (es decir, si deseas poder detectar cambios en varios lugares). Solo unir la entidad tampoco funciona. En mi caso, la solución @BrokenGlass funciona mejor ya que envuelvo todas las llamadas de contexto en los usos. – Bracher

1

La extensión de la respuesta de jfar para proporcionar algunas aclaraciones y cambios que tenía que hacer para conseguir que funcione:

// 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; 
     } 

    } 
} 
Cuestiones relacionadas