Entity Framework 4.1 DbContext переопределяет SaveChanges для аудита изменения свойств - PullRequest
39 голосов
/ 27 мая 2011

Я пытаюсь реализовать ограниченный «журнал аудита» изменений свойств в наборе классов.Я успешно выяснил, как установить свойства типа CreatedOn | ModifiedOn, но не смог найти способ «найти» измененное свойство.

Пример:

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();
    }
}

Итак, есть ли способ получить доступ к свойству, которое изменилось с этим уровнем детализации в EF 4.1 DbContext?

Ответы [ 5 ]

44 голосов
/ 27 мая 2011

Очень, очень грубая идея:

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

Понятия не имею, будет ли это действительно работать в деталях, но я бы попробовал это как первый шаг. Конечно, может быть более одного свойства, которое изменилось, поэтому цикл и, возможно, несколько вызовов WriteAudit.

Отражение внутри SaveChanges может стать кошмаром производительности.

Редактировать

Возможно, лучше получить доступ к базовому ObjectContext. Тогда что-то подобное возможно:

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();
    }
}

Я сам использовал это в EF 4.0. Я не могу найти соответствующий метод для GetModifiedProperties (который является ключом, чтобы избежать кода отражения) в DbContext API.

Редактировать 2

Важно : При работе с объектами POCO приведенный выше код должен вызывать DbContext.ChangeTracker.DetectChanges() в начале. Причина в том, что base.SaveChanges вызывается здесь слишком поздно (в конце метода). base.SaveChanges вызывает DetectChanges внутри, но поскольку мы хотим проанализировать и записать изменения раньше, мы должны вызвать DetectChanges вручную, чтобы EF мог найти все измененные свойства и правильно установить состояния в трекере изменений.

Возможны ситуации, когда код может работать без вызова DetectChanges, например, если методы DbContext / DbSet, такие как Add или Remove, используются после последних изменений свойства, поскольку эти методы также вызывают DetectChanges внутренне. Но если, например, сущность только что загружена из БД, несколько свойств изменяются, и затем вызывается этот производный SaveChanges, автоматическое обнаружение изменений не произойдет до base.SaveChanges, что в итоге приведет к отсутствию записей журнала для измененных свойств.

Я обновил код выше соответственно.

7 голосов
/ 28 мая 2011

Вы можете использовать методы, которые предлагает Слаума, но вместо переопределения метода SaveChanges(), вы можете обработать событие SavingChanges для гораздо более простой реализации.

1 голос
/ 05 января 2013

Мне очень нравится решение Слаумы.Я обычно предпочитаю отслеживать измененную таблицу и записывать первичные ключи.Это очень простой метод, который вы можете использовать для этого, вызывая getEntityKeys(entry)

    public static string getEntityKeys(ObjectStateEntry entry)
    {
        return string.Join(", ", entry.EntityKey.EntityKeyValues
                                .Select(x => x.Key + "=" + x.Value));
    }
1 голос
/ 18 ноября 2011

Кажется, что ответ Слаумы не проверяет изменения внутренних свойств свойства сложного типа.

Если это проблема для вас, мой ответ здесь может оказаться полезным.Если свойство является сложным, и оно изменилось, я сериализирую все комплексное свойство в запись журнала аудита.Это не самое эффективное решение, но оно не так уж плохо, и оно выполняет свою работу.

0 голосов
/ 10 мая 2013

См. Использование Entity Framework 4.1. Отслеживание изменений DbContext для ведения журнала аудита .

С DbEntityEntry.Детальный аудит для добавления, удаления, изменения

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...