Muchas veces nuestros proyectos requieren mantener algún tipo de auditoría en los registros de la base de datos para las inserciones y cambios, de manera que podamos saber quien creó un registro o el último usuario que lo modificó.

Cuando tenemos montones de entidades y mapeos corriendo arriba y abajo por nuestras capas de la aplicación, a veces es difícil saber dónde y cómo tenemos que solucionar esta tarea.

En este artículo mostraré una buena técnica para implementar auditoría de registros usando Entity Framework. Seguramente podrás encontrar otras aproximaciones que puedas considerar mejores, pero ésta es una manera limpia y simple, y de hecho, es la que uso en mis proyectos a diario.

Para la demostración, usaré como base un proyecto que creé para mi artículo anterior “Entity Framework 6.0 code first y TPT, persistencia de colecciones polimórficas“. Lo has leido ya, verdad?

Clonar repositorio: https://joanvilarino@bitbucket.org/joanvilarino/entityframework_tpt.git
Descargar ZIP: https://bitbucket.org/joanvilarino/entityframework_tpt/downloads

Entidad Auditada

Vamos a añadir una nueva clase BaseAuditedEntity que heredará de BaseEntity, entonces haremos que el resto de nuestras entidades hereden de ella. Esto no es obligatorio, ya que puedes heredar solo algunas de ellas, quiero decir, las que realmente quieras auditar en tu base de datos.

En nuestra entidad base auditada, solo crearemos los campos que necesitamos para saber quien y cuando editaron o crearon el registro.

public class BaseAuditedEntity : BaseEntity
{
    public DateTime Created { get; set; }
    public string CreatedBy { get; set; }
    public DateTime? Updated { get; set; }
    public string UpdatedBy { get; set; }
}

Ahora cambiaremos Car y CarPart (ésta última nuestra entidad base para las piezas del coche, de la que heredan todas las demás) para que hereden de BaseAuditedEntity

public class Car : BaseAuditedEntity
{
    // contents of the class
}

public class CarPart : BaseAuditedEntity
{
    // contents of the class
}

Éste es mi Kung-fú

Ok, ya tenemos las entidades, ahora cómo y dónde rellenamos los datos?

La solución está en nuestro contexto de Entity Framework, que llamamos BoundContext. Allí, podemos hacer override del método SaveChanges para que procese las entidades que van a ser guardadas en la base de datos antes de que sean realmente guardadas.

La clase DBContext de Entity Framework tiene una propiedad llamada ChangeTracker, que contiene, además de otras cosas, todas las entidades que van a ser escritas a la base de datos, incluyendo las nuevas, las actualizadas y las borradas en una colección que devuelve el método genérico Entries.

Aquí es donde vamos a “enganchar” y comprobar las entidades que se van a grabar para rellenar nuestros campos de auditoria, comprobando la operación que se va a hacer en cada una de las entidades.

public override int SaveChanges()
{

    ChangeTracker.Entries<BaseAuditedEntity>().ToList().ForEach(
        entry =>
        {
            var auditedEntity = entry.Entity;
            if (entry.State == EntityState.Added)
            {
                auditedEntity.Created = DateTime.Now;
                auditedEntity.CreatedBy = GetCurrentUser();
            }
            if (entry.State == EntityState.Modified)
            {
                auditedEntity.Updated = DateTime.Now;
                auditedEntity.UpdatedBy = GetCurrentUser();
            }
        });

    return base.SaveChanges();
}

Como puedes ver, obtenemos todas las entidades que son (o heredan de) BaseAuditedEntity que hay en la colección Entry, y entonces comprobamos su propiedad State para saber si iba a ser añadida (Added), actualizada (Modified) o borrada (Deleted) aunque esta última no nos interesa a menos que queramos hacer un borrado “virtual” (es decir, no borrarla físicamente, sino marcar un campo como “borrado”).

Finalmente, solo tenemos que llamar a base.SaveChanges() para que Entity Framework siga quemando goma, eso es todo!

Consideraciones acerca del usuario actual

Auditar el usuario correcto puede ser un poco “truco”. Como ves en el ejemplo, estoy usando un método privado llamado GetCurrentUser() que devuelve el usuario de Windows actual, o el string “Anonymous”.

private string GetCurrentUser()
{
    var currentUser = System.Security.Principal.WindowsIdentity.GetCurrent();
    return (currentUser != null) ? currentUser.Name : "Anonymous";
}

El problema es que, en muchos casos nuestra infraestructura puede estar en un servidor usando un solo usuario para toda la aplicacion, en una cuenta de sistema o preparada para la base de datos, y no conoce realmente al usuario de front-end o webservice que está haciendo la llamada.

En esas situaciones, podrías simplemente pasar a través de las capas un currentUser a tu contexto llamando a SaveChanges() desde tu Unit Of Work o Repositorio, y todo iría igual de bien.

Gracias!

Bueno, esto es todo por hoy, si todavía estas hambriento de conocimiento, siempre puedes leer cualquier de mis exquisitos artículos anteriores, o ir a lo bruto y descagar cualquiera de las soluciones de demostración en mi repositorio BitBucket. Paz!

Follow me

Joan Vilariño

Senior .NET Developer at Ohpen
Follow me