39 votes

Entity Framework 4.1 DbContext Override SaveChanges pour auditer le changement de propriété

J'essaie de mettre en œuvre un "journal d'audit" contraint des modifications apportées aux propriétés d'un ensemble de classes. J'ai réussi à trouver comment définir des propriétés de type CreatedOn|ModifiedOn, mais je ne parviens pas à trouver comment "trouver" la propriété qui a été modifiée.

Exemple :

public class TestContext : DbContext
{
    public override int SaveChanges()
    {
        var utcNowAuditDate = DateTime.UtcNow;
        var changeSet = ChangeTracker.Entries<IAuditable>();
        if (changeSet != null)
            foreach (DbEntityEntry<IAuditable> dbEntityEntry in changeSet)
            {

                switch (dbEntityEntry.State)
                {
                    case EntityState.Added:
                        dbEntityEntry.Entity.CreatedOn = utcNowAuditDate;
                        dbEntityEntry.Entity.ModifiedOn = utcNowAuditDate;
                        break;
                    case EntityState.Modified:
                        dbEntityEntry.Entity.ModifiedOn = utcNowAuditDate;
                        //some way to access the name and value of property that changed here
                        var changedThing = SomeMethodHere(dbEntityEntry);
                        Log.WriteAudit("Entry: {0} Origianl :{1} New: {2}", changedThing.Name,
                                        changedThing.OrigianlValue, changedThing.NewValue)
                        break;
                }
            }
        return base.SaveChanges();
    }
}

Existe-t-il donc un moyen d'accéder à la propriété qui a été modifiée avec ce niveau de détail dans EF 4.1 DbContext ?

44voto

Slauma Points 76561

Une idée très, très sommaire :

foreach (var property in dbEntityEntry.Entity.GetType().GetProperties())
{
    DbPropertyEntry propertyEntry = dbEntityEntry.Property(property.Name);
    if (propertyEntry.IsModified)
    {
        Log.WriteAudit("Entry: {0} Original :{1} New: {2}", property.Name,
            propertyEntry.OriginalValue, propertyEntry.CurrentValue);
    }
}

Je n'ai aucune idée si cela fonctionnerait vraiment dans le détail, mais c'est quelque chose que j'essaierais dans un premier temps. Bien sûr, il pourrait y avoir plus d'une propriété qui a changé, donc la boucle et peut-être plusieurs appels de WriteAudit .

Le truc de réflexion à l'intérieur de SaveChanges pourrait devenir un cauchemar de performance cependant.

Modifier

Il est peut-être préférable d'accéder au sous-jacent ObjectContext . Alors quelque chose comme ceci est possible :

public class TestContext : DbContext
{
    public override int SaveChanges()
    {
        ChangeTracker.DetectChanges(); // Important!

        ObjectContext ctx = ((IObjectContextAdapter)this).ObjectContext;

        List<ObjectStateEntry> objectStateEntryList =
            ctx.ObjectStateManager.GetObjectStateEntries(EntityState.Added
                                                       | EntityState.Modified 
                                                       | EntityState.Deleted)
            .ToList();

       foreach (ObjectStateEntry entry in objectStateEntryList)
       {
           if (!entry.IsRelationship)
           {
               switch (entry.State)
               {
                   case EntityState.Added:
                       // write log...
                       break;
                   case EntityState.Deleted:
                       // write log...
                       break;
                   case EntityState.Modified:
                   {
                       foreach (string propertyName in
                                    entry.GetModifiedProperties())
                       {
                           DbDataRecord original = entry.OriginalValues;
                           string oldValue = original.GetValue(
                               original.GetOrdinal(propertyName))
                               .ToString();

                           CurrentValueRecord current = entry.CurrentValues;
                           string newValue = current.GetValue(
                               current.GetOrdinal(propertyName))
                               .ToString();

                           if (oldValue != newValue) // probably not necessary
                           {
                               Log.WriteAudit(
                                   "Entry: {0} Original :{1} New: {2}",
                                   entry.Entity.GetType().Name,
                                   oldValue, newValue);
                           }
                       }
                       break;
                   }
               }
           }
       }
       return base.SaveChanges();
    }
}

Je l'ai moi-même utilisé dans EF 4.0. Je ne trouve pas de méthode correspondante à GetModifiedProperties (ce qui est la clé pour éviter le code de réflexion) dans le fichier DbContext API.

Edit 2

Important : Lorsque l'on travaille avec des entités POCO, le code ci-dessus doit appeler DbContext.ChangeTracker.DetectChanges() au début. La raison en est que base.SaveChanges est appelé trop tard ici (à la fin de la méthode). base.SaveChanges appelle DetectChanges en interne, mais parce que nous voulons analyser et enregistrer les changements avant, nous devons appeler DetectChanges manuellement afin que EF puisse trouver toutes les propriétés modifiées et définir correctement les états dans le suivi des modifications.

Il y a des situations possibles où le code peut fonctionner sans appeler DetectChanges Par exemple, si des méthodes DbContext/DbSet telles que Add o Remove sont utilisées après les dernières modifications de propriétés puisque ces méthodes appellent également DetectChanges en interne. Mais si, par exemple, une entité est simplement chargée à partir de la base de données, que quelques propriétés sont modifiées et qu'ensuite cette dérivée SaveChanges est appelé, la détection automatique des changements ne se ferait pas avant que base.SaveChanges Il en résulte finalement des entrées de journal manquantes pour les propriétés modifiées.

J'ai mis à jour le code ci-dessus en conséquence.

7voto

Jay Points 4474

Vous pouvez utiliser les méthodes suggérées par Slauma, mais au lieu de redéfinir la fonction SaveChanges() vous pouvez gérer la méthode SavingChanges pour une mise en œuvre beaucoup plus facile.

1voto

fsmmu Points 616

Il semble que la réponse de Slauma ne vérifie pas les modifications apportées aux propriétés internes d'une propriété de type complexe.

Si c'est un problème pour vous, ma réponse est la suivante ici pourrait être utile. Si la propriété est complexe et qu'elle a été modifiée, je sérialise l'ensemble de la propriété complexe dans l'enregistrement du journal d'audit. Ce n'est pas la solution la plus efficace, mais ce n'est pas si mal et cela permet de faire le travail.

1voto

Vland Points 2275

J'aime beaucoup la solution de Slauma. Je préfère généralement garder la trace de la table modifiée et des clés primaires des enregistrements. Voici une méthode très simple que vous pouvez utiliser pour faire cela, en appelant getEntityKeys(entry)

    public static string getEntityKeys(ObjectStateEntry entry)
    {
        return string.Join(", ", entry.EntityKey.EntityKeyValues
                                .Select(x => x.Key + "=" + x.Value));
    }

0voto

Voir Utilisation du suivi des changements de DbContext d'Entity Framework 4.1 pour la journalisation des audits .

Avec DbEntityEntry. Audit détaillé pour l'ajout, la suppression et la modification

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X