To automatically fill fields when saving object in database

Asked

Viewed 377 times

8

I have a system, in ASP.NET MVC which implements a form of audit, that when a user enters or updates an object into the database, some information is saved to 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 about how to actually implement, so that save in a table the user who made the insertion or update of this data.

1 answer

8


Note 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 like this:

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 take the fields that have been modified this way:

        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 audit in which each entity has its dedicated audit table, with all fields of the original table and a few more. Then 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 be 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, so:

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.

  • Then the class public override int SaveChanges() which I used in the question is not used?

  • It is used yes. The code of the mirror audit goes inside it.

Browser other questions tagged

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