Dynamically check which attributes of a Model have changed?

5

I'm developing an ASP.NET MVC 4 application and need to save the log of every change made to a Model object.

Is there any native method or implementation already known that does this? Example:

public class Pessoa
{
    public int? Id { get; set; }
    public string Nome { get; set; }
    public string SobreNome { get; set; }
}

Let's assume that the person just modified the OverName I want to know the old value of the current one and that this attribute has changed.

    
asked by anonymous 14.08.2014 / 20:14

4 answers

2

As I did not find anything ready on the internet with the help of a friend, we created a class and a generic method to do this:

public class AlteracaoLog
{
    public string ObjName { get; set; }
    public string Property { get; set; }
    public string OldValue { get; set; }
    public string NewValue { get; set; }

    /// <summary>
    /// Método para comparar diferenças entre dois objetos.
    /// </summary>
    /// <typeparam name="T1">Tipo do Objeto</typeparam>
    /// <param name="oldObj">Objeto Original</param>
    /// <param name="newObj">Objeto que você quer comparar</param>
    /// <param name="typeObj">Opcional, é usado quando a chamada é feita recursivamente</param>
    /// <returns>Array de AlteracaoLog</returns>
    public static AlteracaoLog[] Diff<T>(T oldObj, T newObj, Type typeObj = null)
    {
        var diffList = new List<AlteracaoLog>();

        var type = typeObj ?? typeof(T);

        foreach (var prop in type.GetProperties())
        {
            var _newValue = prop.GetValue(newObj, null);
            var _oldValue = prop.GetValue(oldObj, null);

            // Caso o objeto tenha uma propriedade que implemente a Interface "IBase"
            // Verifico as alterações desse objeto recursivamente e adiciono na Lista.
            if (prop.PropertyType.GetInterfaces().Contains(typeof(IBase)))
            {
                var tes = Diff(_oldValue, _newValue, prop.PropertyType);
                diffList.AddRange(tes);
            }
            else
            {
                if (!_newValue.Equals(_oldValue))
                {
                    diffList.Add(new AlteracaoLog
                    {
                        ObjName = type.Name,
                        Property = prop.Name,
                        OldValue = _oldValue.ToString(),
                        NewValue = _newValue.ToString()
                    });
                }
            }
        }
        return diffList.ToArray();
    }
}
    
15.08.2014 / 13:44
4

I usually use the patern Observer .

  

Observer is a Design Pattern that represents a 1-N (one-to-many) relationship between objects. So when an object changes state dependent objects will be notified / updated automatically. This pattern allows objects to be warned of the state change of other events occurring in another object.

     

Observer is also called Publisher-Subscriber, Event Generator and Dependents.

     

The components of this patern are:

     
  • Subject

         
    • Know the Observers. Any number of Observer objects can observe a Subject.
    •   
    • Provides interfaces for attaching and detaching Observer objects.
    •   
  •   
  • ConcreteSubject

         
    • Stores state of interest for ConcreteObserver.
    •   
    • Send notification to observers when status changes.
    •   
  •   
  • Observer

         
    • Defines an update interface for objects that should be notified of changes to a Subject.
    •   
  •   
  • ConcreteObserver

         
    • Maintains a reference to a ConcreteSubject object.
    •   
    • Stores the state that should be consistent with the Subject.
    •   
    • Implements the Observer interface by updating to keep the status consistent with the Subject.
    •   
  •   

You can look at this link to see a sample code

    
15.08.2014 / 14:58
2

If you are using the Entity Framework and your attempt is to create a kind of change log, you can override the SaveChanges() method of your DbContext implementation and check changes in ChangeTracker :

    public override int SaveChanges()
    {
        if (ChangeTracker.HasChanges())
        {
            var userLogado = System.Security.Principal.WindowsPrincipal.Current.Identity.Name;
            var entries = ChangeTracker.Entries();
            foreach (var entry in entries)
            {                    
                if (entry.State == EntityState.Added)
                {
                }

                if (entry.State == EntityState.Deleted)
                {
                }

                if (entry.State == EntityState.Modified)
                {
                    //entry.OriginalValues; 
                    //entry.CurrentValues; 
                    //entry.Entity;                                                
                }
            }

        }

        return base.SaveChanges();
    }

As we see in the code snippet, it is possible to detect in which change state entry.State is an entry and check the original values entry.OriginalValues and current entry.CurrentValues , in addition to accessing the entry.Entity entity itself.

Also in the example, userLogado gives access to the user who made the change.

By reflection it is possible to compare the original and current values to detect only the modified fields.

    
16.08.2014 / 20:21
1
using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Linq;
using System.Reflection; 
using System.Text; 
using System.Threading.Tasks;

namespace CrashMemory
{
    public class AlteracaoLog
    {
        public string ObjName { get; set; }
        public string Property { get; set; }
        public string OldValue { get; set; }
        public string NewValue { get; set; }

        /// <summary>
        /// Método para comparar diferenças entre dois objetos.
        /// </summary>
        /// <typeparam name="T1">Tipo do Objeto</typeparam>
        /// <param name="oldObj">Objeto Original</param>
        /// <param name="newObj">Objeto que você quer comparar</param>
        /// <param name="typeObj">Opcional, é usado quando a chamada é feita recursivamente</param>
        /// <returns>Array de AlteracaoLog</returns>
        public static AlteracaoLog[] Diff<T>(T oldObj, T newObj, Type typeObj = null)
        {
            var diffList = new List<AlteracaoLog>();
            var type = typeObj ?? typeof(T);
            //var reg = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField);
            //var reg2 = type.GetProperties();

            //var propriedades = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.GetField).Where(a => a.MemberType == MemberTypes.Property || a.MemberType == MemberTypes.Field);
            foreach (var prop in type.GetFields())
            {
                var nome = prop.Name;
                //verificando se é um array ou lista 
                if (prop.GetValue(newObj).GetType().IsGenericType && prop.GetValue(oldObj).GetType().IsGenericType)
                {
                    //Tipo da lista ou tipo da classe do objecto
                    Type tipo = (Type)GetGenericCollectionItemType(prop.GetValue(newObj).GetType());

                    var oldObjField = (IList)prop.GetValue(oldObj);
                    var newObjField = (IList)prop.GetValue(newObj);
                    var tipoObjeto = prop.GetValue(newObj).GetType();

                    var countNewObjField = (int)tipoObjeto.GetProperty("Count").GetValue(newObjField, null);
                    var countOldObjField = (int)tipoObjeto.GetProperty("Count").GetValue(oldObjField, null);


                    if (countNewObjField > countOldObjField)
                    {
                        for (int i = 0; i < countNewObjField; i++)
                        {
                            if (i >= countOldObjField)
                            {
                                var instanciaNula = Activator.CreateInstance(tipo);
                                diffList.AddRange(Diff(instanciaNula, newObjField[i], newObjField[i].GetType()));
                            }
                            else
                            {
                                diffList.AddRange(Diff(oldObjField[i], newObjField[i], newObjField[i].GetType()));
                            }
                        }
                    }
                    else
                        if (countOldObjField > countNewObjField)
                        {
                            for (int i = 0; i < countOldObjField; i++)
                            {
                                if (i >= countNewObjField)
                                {
                                    var instanciaNula = Activator.CreateInstance(tipo);
                                    diffList.AddRange(Diff(oldObjField[i], instanciaNula, oldObjField[i].GetType()));
                                }
                                else
                                {
                                    diffList.AddRange(Diff(oldObjField[i], newObjField[i], newObjField[i].GetType()));
                                }
                            }
                        }
                        else
                        {
                            for (int i = 0; i < countNewObjField; i++)
                            {
                                diffList.AddRange(Diff(oldObjField[i], newObjField[i], newObjField[i].GetType()));
                            }
                        }

                }
                else
                {
                    var _newValue = prop.GetValue(newObj);
                    var _oldValue = prop.GetValue(oldObj);
                    diffList.AddRange(Diff(_oldValue, _newValue, _oldValue.GetType()));
                }


            }






            foreach (var prop in type.GetProperties())
            {
                var _newValue = prop.GetValue(newObj, null);
                var _oldValue = prop.GetValue(oldObj, null);
                _newValue = _newValue == null ? "" : _newValue;

                if (!_newValue.Equals(_oldValue))
                {
                    diffList.Add(new AlteracaoLog
                    {
                        ObjName = type.Name,
                        Property = prop.Name,
                        OldValue = _oldValue == null ? "" : _oldValue.ToString(),
                        NewValue = _newValue == null ? "" : _newValue.ToString()
                    });
                }
                //}
            }
            return diffList.ToArray();
        }


        public static List<AlteracaoLog> FieldChanged(Type type, object newObj, object oldObj)
        {

            var fields = type.GetFields();
            var diffList = new List<AlteracaoLog>();
            foreach (var prop in fields)
            {
                var _newValue = prop.GetValue(newObj);
                var _oldValue = prop.GetValue(oldObj);
                diffList.AddRange(Diff(_oldValue, _newValue, _oldValue.GetType()));
            }
            return diffList.ToList();
        }

        static Type GetGenericCollectionItemType(Type type)
        {
            if (type.IsGenericType)
            {
                var args = type.GetGenericArguments();
                if (args.Length == 1 &&
                    typeof(ICollection<>).MakeGenericType(args).IsAssignableFrom(type))
                {
                    return args[0];
                }
            }
            return null;
        }
    }
}
    
08.03.2016 / 20:26