How to automatically fill fields when saving object to bank

8

I have a system, in ASP.NET MVC that implements an audit form, that when a user inserts or updates an object in the database, some information is saved in an audit table automatically.

The interface, which I want to implement is:

public interface IEntidade
{
    DateTime DataCriacao { get; set; }
    String UsuarioCriacao { get; set; }
    DateTime UltimaModificacao { get; set; }
    String UsuarioModificacao { get; set; }
}

I also have an example of SaveChanges(); in my class that inherits from DbContext

public override int SaveChanges()
   {
        try
        {
            var currentTime = DateTime.Now;

            foreach (var entry in ChangeTracker.Entries().Where(e => e.Entity != null &&
                    typeof(IEntidadePrimitiva).IsAssignableFrom(e.Entity.GetType())))
            {
                if (entry.State == EntityState.Added)
                {                       

                    if (entry.Property("DataCriacao") != null)
                    {
                        entry.Property("DataCriacao").CurrentValue = currentTime;
                    }
                    if (entry.Property("UsuarioCriacao") != null)
                    {
                        entry.Property("UsuarioCriacao").CurrentValue = HttpContext.Current != null ? HttpContext.Current.User.Identity.Name : "Usuario";
                    }
                }

                if (entry.State == EntityState.Modified)
                {
                    entry.Property("DataCriacao").IsModified = false;
                    entry.Property("UsuarioCriacao").IsModified = false;

                    if (entry.Property("UltimaModificacao") != null)
                    {
                        entry.Property("UltimaModificacao").CurrentValue = currentTime;
                    }
                    if (entry.Property("UsuarioModificacao") != null)
                    {
                        entry.Property("UsuarioModificacao").CurrentValue = HttpContext.Current != null ? HttpContext.Current.User.Identity.Name : "Usuario";
                    }
                }
            }

            return base.SaveChanges();
        }
        catch (DbEntityValidationException ex)
        {
            var errorMessages = ex.EntityValidationErrors
                .SelectMany(x => x.ValidationErrors)
                .Select(x => x.ErrorMessage);

            var fullErrorMessage = string.Join("; ", errorMessages);

            var exceptionsMessage = string.Concat(ex.Message, "Os erros de validações são: ", fullErrorMessage);

            throw new DbEntityValidationException(exceptionsMessage, ex.EntityValidationErrors);
        }
    }

But what I am in doubt, is how to actually implement, so you can save the user who has inserted or updated this data into a table.

    
asked by anonymous 02.12.2016 / 00:54

1 answer

8

Notice that the code to save the user is here:

if (entry.Property("UsuarioModificacao") != null)
{
    entry.Property("UsuarioModificacao").CurrentValue = HttpContext.Current != null ? HttpContext.Current.User.Identity.Name : "Usuario";
}

The principle is simple: if there is a global audit table for the system, you can map it as follows:

public class Auditoria
{
    [Key]
    public int AuditoriaId { get; set; }
    public string EntidadeModificada { get; set; }
    public int IdentificadorEntidadeId { get; set; }
    public string NomeCampoModificado { get; set; }
    public string ValorAntigo { get; set; }
    public string ValorNovo { get; set; }
    public DateTime DataModificacao { get; set; }
    public string UsuarioModificacao { get; set; }
}

Then you can get the fields that have been modified like this:

        var camposModificados = context.ChangeTracker.Entries()
        .Where (t => t.State == EntityState.Modified)
        .Select (t => new {
            EntidadeModificada = t.Entity.GetType().Name,
            ValorAntigo = t.OriginalValues.PropertyNames.ToDictionary (pn => pn, pn => t.OriginalValues[pn]),
            ValorNovo = t.CurrentValues.PropertyNames.ToDictionary (pn => pn, pn => t.CurrentValues[pn]),
        });

This form is what we call incremental audit or delta audit , where only modifications are audited.

There is another form of auditing where each entity has its dedicated auditing table, with all fields in the original table and a few more. It would be interesting to change your interface to:

public interface IEntidade<TClasseAuditada>
    where TClasseAuditada: class
{
    DateTime DataCriacao { get; set; }
    String UsuarioCriacao { get; set; }
    DateTime? UltimaModificacao { get; set; }
    String UsuarioModificacao { get; set; }
}

Your class will look something like:

public class MinhaClasse : IEntidade<MinhaClasseAuditoria> { ... }
public class MinhaClasseAuditoria { /* Mesmos campos de MinhaClasse, com usuário e data de modificação */ }

In this case, you would have to make the context dynamically create the audit class, like this:

SeuContexto.cs , SaveChanges() and SaveChangesAsync()

foreach (var entidade in ChangeTracker.Entries())
{
    var tipoTabelaAuditoria = entidade.Entity.GetType().GetInterfaces()[0].GenericTypeArguments[0];
    var registroTabelaAuditoria = Activator.CreateInstance(tipoTabelaAuditoria);

    // Isto aqui é lento, mas serve como exemplo. 
    // Depois procure trocar por FastMember ou alguma outra estratégia de cópia.
    foreach (var propriedade in entidade.Entity.GetType().GetProperties())
    {
        registroTabelaAuditoria.GetType()
                               .GetProperty(propriedade.Name)
                               .SetValue(registroTabelaAuditoria, entidade.Entity.GetType().GetProperty(propriedade.Name).GetValue(entidade.Entity, null));
    }

    /* Salve aqui usuário e data */
    this.Set(registroTabelaAuditoria.GetType()).Add(registroTabelaAuditoria);
}

This is what we call Mirror Audit .

    
02.12.2016 / 01:40