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. Детальный аудит для добавления, удаления, изменения