Implementación de Registro de Auditoría / Historial de Cambios con MVC y Entity Framework


Estoy creando un Historial de Cambios / Registro de auditoría en mi aplicación MVC que está utilizando Entity Framework.

Así que específicamente en el método de edición public ActionResult Edit(ViewModel vm), encontramos el objeto que estamos tratando de actualizar, y luego usamos TryUpdateModel(object) para transponer los valores del formulario al objeto que estamos tratando de actualizar.

Quiero registrar un cambio cuando cambie cualquier campo de ese objeto. Así que básicamente lo que necesito es una copia del objeto antes de que se edite y luego compararlo después de que TryUpdateModel(object) haya hecho su trabajo. es decir,

[HttpPost]
public ActionResult Edit(ViewModel vm)
{
    //Need to take the copy here
    var object = EntityFramework.Object.Single(x=>x.ID = vm.ID);

    if (ModelState.IsValid)
    {
        //Form the un edited view model
        var uneditedVM = BuildViewModel(vm.ID); //this line seems to confuse the EntityFramework (BuildViewModel() is used to build the model when originally displaying the form)
        //Compare with old view model
        WriteChanges(uneditedVM, vm);
        ...
        TryUpdateModel(object);
    }
    ...
}

Pero el problema es que cuando el código recupera la "vm no editada", esto está causando algunos cambios inesperados en EntityFramework, de modo que TryUpdateModel(object); lanza un UpdateException.

Así que la pregunta es - en esta situación - ¿cómo puedo crear una copia del object fuera de EntityFramework para comparar el historial de cambios / auditorías,de modo que no afecte o cambie el EntityFramework at all

Editar: No quiero usar disparadores. Necesidad de registrar el nombre de usuario quién haciéndolo.

Edit1: Usando EFv4, no estoy muy seguro de cómo sobreescribir SaveChanges() pero puede ser una opción


Esta ruta parece no ir a ninguna parte, para un requisito tan simple! Finalmente conseguí que se anule correctamente, pero ahora tengo una excepción con ese código:

public partial class Entities
{
    public override int SaveChanges(SaveOptions options)
    {
        DetectChanges();
        var modifiedEntities = ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
        foreach (var entry in modifiedEntities)
        {
            var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry).GetModifiedProperties(); //This line throws exception The ObjectStateManager does not contain an ObjectStateEntry with a reference to an object of type 'System.Data.Objects.EntityEntry'.
            var currentValues = ObjectStateManager.GetObjectStateEntry(entry).CurrentValues;
            foreach (var propName in modifiedProps)
            {
                var newValue = currentValues[propName];
                //log changes
            }
        }

        //return base.SaveChanges();
        return base.SaveChanges(options);
    }
}
Author: baron, 2011-07-29

3 answers

SI está utilizando EF 4, puede suscribirse al evento SavingChanges.

Dado que Entities es una clase parcial, puede agregar funcionalidad adicional en un archivo separado. Así que crea un nuevo archivo llamado Entities y allí implementa el método parcial OnContextCreated para conectar el evento

public partial class Entities
{
    partial void OnContextCreated()
    {
        SavingChanges += OnSavingChanges;
    }

    void OnSavingChanges(object sender, EventArgs e)
    {

        var modifiedEntities = ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
        foreach (var entry in modifiedEntities)
        {
            var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
            var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
            foreach (var propName in modifiedProps)
            {
                var newValue = currentValues[propName];
                //log changes
            }
        }
    }
}

Si está utilizando EF 4.1, puede consultar este artículo para extraer los cambios

 23
Author: Eranga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-29 05:50:23

Vea FrameLog, una biblioteca de registro de Entity Framework que escribí para este propósito. Es de código abierto, incluso para uso comercial.

Sé que preferiría ver un fragmento de código que muestre cómo hacer esto, pero para manejar correctamente todos los casos para el registro, incluidos los cambios en las relaciones y los cambios de muchos a muchos, el código se vuelve bastante grande. Esperemos que la biblioteca sea un buen ajuste para sus necesidades, pero si no puede adaptar libremente el código.

FrameLog puede registrar cambia todas las propiedades escalares y de navegación, y también le permite especificar un subconjunto que le interesa registrar.

 15
Author: Martin Eden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-17 15:53:22

Hay un artículo con alta calificación aquí en el codeproject: Implementando Audit Trail usando Entity Framework . Parece hacer lo que quieres. He empezado a utilizar esta solución en un proyecto. Primero escribí disparadores en T-SQL en la base de datos, pero era demasiado difícil mantenerlos con los cambios en el modelo de objetos sucediendo todo el tiempo.

 1
Author: Patrik Lindström,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-08 20:27:19