Entity Framework 4.1 DbContext переопределяет SaveChanges для аудита изменения свойств

Я пытаюсь реализовать ограниченный "журнал аудита" изменений свойств в набор классов. Я успешно выяснил, как установить свойства типа 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 ответов

Решение

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

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в результате чего отсутствуют записи в журнале для измененных свойств.

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

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

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

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

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

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

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

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

Другие вопросы по тегам