Dynamically check which attributes of a Model have been changed?

Asked

Viewed 1,684 times

5

I am developing an ASP.NET MVC 4 application and need to save the log of each change made in a Model object.

Is there any native method or implementation already known to do 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 only modified the Last name I want to know the old value the current and that this attribute was changed.

  • Do you use Entity framework? If yes, you can use this project: https://github.com/loresoft/EntityFramework.Extended/wiki/Audit-Log

4 answers

4

I usually use the patern Observer.

Observer is a Design Pattern that represents a 1-N (one-to-many) relationship between objects. Thus when an object changes state the dependent objects will be notified/informed and updated automatically. This pattern allows objects to be warned of the change of state 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 the Concreteobserver.
    • Send notification to Observers when status changes.
  • Observer

    • Sets an update interface for objects that must be notified of changes in a Subject.
  • Concreteobserver

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

You can look at this link to see an example code

  • Complementing: there is the Inotifypropertychanged that does that too.

  • @NULL I don’t work with . net, so I passed a general approach. If you find it interesting you can complement my answer.

2

If you are using Entity Framework and your attempt is to create a kind of change log, you can override the method SaveChanges() of its implementation of DbContext 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 state of change entry.State if you find an entry and check the original values entry.OriginalValues and current entry.CurrentValues, in addition to access to the entry.Entity in 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.

2


Since I didn’t 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();
    }
}

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;
        }
    }
}
  • 1

    You can describe what your changes were in the original response?

  • Hello all right? Verification, if the object has Fields (foreach (var prop in type.Getfields())) ex: person {name, age ,List<Addresses>} a person who besides name and age has an address instance, previously only checked properties,now interacts with other objects and lists of the class (e.g., List<Addresses>}) recursively and independently of the size between them and check for null values that may cause exceptions.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.