web-dev-qa-db-fra.com

Implémentation du journal d'audit / de l'historique des modifications avec MVC et Entity Framework

Je crée un historique des modifications/journal d'audit dans mon application MVC qui utilise Entity Framework.

Donc, spécifiquement dans la méthode d'édition public ActionResult Edit(ViewModel vm), nous trouvons l'objet que nous essayons de mettre à jour, puis utilisons TryUpdateModel(object) pour transposer les valeurs du formulaire vers l'objet que nous essayons de mise à jour.

Je souhaite enregistrer une modification lorsqu'un champ de cet objet change. Donc, fondamentalement, j'ai besoin d'une copie de l'objet avant de le modifier, puis de le comparer après que la TryUpdateModel(object) a fait son travail. c'est à dire.

[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);
    }
    ...
}

Mais le problème est que lorsque le code récupère le "vm non édité", cela provoque des modifications inattendues dans EntityFramework - de sorte que TryUpdateModel(object); jette un UpdateException.

La question est donc - dans cette situation - comment créer une copie du object en dehors de EntityFramework pour comparer l'historique des modifications/audits, afin qu'il n'affecte ni ne modifie le EntityFramework du tout

modifier: Je ne veux pas utiliser de déclencheurs. Besoin de connecter le nom d'utilisateur qui l'a fait.

edit1: En utilisant EFv4, je ne sais pas trop comment contourner SaveChanges() mais cela peut être une option


Cette route semble aller nulle part, pour une exigence aussi simple! J'ai finalement réussi à le remplacer correctement, mais maintenant je reçois une exception avec ce code:

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);
    }
}
31
baron

SI vous utilisez EF 4, vous pouvez vous abonner à l'événement SavingChanges.

Étant donné que Entities est une classe partielle, vous pouvez ajouter des fonctionnalités supplémentaires dans un fichier séparé. Créez donc un nouveau fichier nommé Entities et implémentez la méthode partielle OnContextCreated pour brancher l'événement

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 vous utilisez EF 4.1, vous pouvez passer par cet article pour extraire les modifications

23
Eranga

Voir FrameLog , une bibliothèque de journalisation Entity Framework que j'ai écrite à cet effet. Il est open source, y compris pour un usage commercial.

Je sais que vous préférez simplement voir un extrait de code montrant comment procéder, mais pour gérer correctement tous les cas de journalisation, y compris les changements de relation et les changements plusieurs à plusieurs, le code devient assez volumineux. Si tout va bien la bibliothèque sera un bon ajustement pour vos besoins, mais sinon vous pouvez librement adapter le code.

FrameLog peut enregistrer les modifications apportées à toutes les propriétés scalaires et de navigation, et vous permet également de spécifier un sous-ensemble que vous souhaitez enregistrer.

15
Martin Eden

Il y a un article avec une note élevée ici sur le projet de code: Implémentation d'Audit Trail en utilisant Entity Framework . Il semble faire ce que tu veux. J'ai commencé à utiliser cette solution dans un projet. J'ai d'abord écrit des déclencheurs en T-SQL dans la base de données, mais il était trop difficile de les maintenir avec des changements dans le modèle objet qui se produisaient tout le temps.

1
Patrik Lindström