2016-07-19 2 views
1

Я некоторое время боролся с проблемой, которая заключается в аудите общих объектов базы данных при их сохранении. У меня есть проект, который использует EF 6, и мне потребовалось создать «неинвазивный» метод для аудита объектов, когда они будут добавлены, изменены или удалены. Я должен хранить JSON вставленного объекта, измененного объекта или удаленного объекта, не мешая нормальному потоку. Проект имеет реализацию Database First.Аудит Entity Framework удаленные записи, сохраняющие предыдущее значение

Мое решение было простым, добавьте частичный класс любого объекта, который остальная часть программистов хочет провести аудит внедрения IAudit, который в основном представляет собой пустой интерфейс для получения всех изменений от объектов, которые его реализуют.

public interface IAudit {} 

У меня есть сущность валюты, только реализовать его без какого-либо другого кода (я мог бы сделать что-то еще в будущем, но я не нужен)

public partial class Currencies : IAudit 

Я переопределить метод SaveChanges к искать для субъектов аудита

public override int SaveChanges() 
{ 
    ChangeTracker.DetectChanges(); 

    // This linq looks for new entities that were marked for audit 
    CreateAuditLog(System.Data.Entity.EntityState.Added); 
    CreateAuditLog(System.Data.Entity.EntityState.Modified); 
    CreateAuditLog(System.Data.Entity.EntityState.Deleted); 

    return base.SaveChanges(); 
} 

решение вызывает в 3 раза CreateAuditLog потому, что в ближайшем будущем мне нужно реализовать конфигурацию для аудита любой пользователь решает, может быть фр om конфигурация базы данных, которая активируется/деактивируется пользователями.

Все работало отлично, я был в состоянии получить сохраненные объекты в указанном состоянии:

private void CreateAuditLog(System.Data.Entity.EntityState state) 
    { 
     var auditedEntities = ChangeTracker.Entries<IAudit>() 
      .Where(p => p.State == state) 
      .Select(p => p.Entity); 

     ... some code that do something else 

     foreach (var auditedEntity in auditedEntities) 
     { 
      ... some information I required to add 

     strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter()); 

      ... some code to save audit information 

     } 

    } 

Проблема в том я теряю каждое значение в Удаляются состояние, я получаю только идентификатор, нет никакой информации в свойства, за исключением идентификатора, и нет никакой возможности его извлечь. Я искал каждое решение в StackOverflow и других веб-сайтах, и восстановить исходную информацию нечего. Как я могу получить предыдущие удаленные значения для их хранения так же, как я храню Добавленные и измененные объекты?

+0

Я работаю над библиотекой, которая может помочь. Взгляните на библиотеку [Audit.EntityFramework] (https://www.nuget.org/packages/Audit.entityframework/), она перехватывает SaveChanges() и может быть настроена для фильтрации объектов, которые вы хотите проверить. – thepirat000

ответ

0

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

Во-первых, поскольку я просто проверяю Удалить по-другому. Я разделял удаленное состояние из добавленного и измененного, которые хорошо работают без изменений. Удаленное состояние - это частный случай, и я отношусь к нему так.

Во-первых, мне нужно было получить исходные значения из базы данных. В состоянии «Удалено» их нет, нет возможности восстановить их из объекта. Можно получить их с помощью следующего кода:

var databaseValues = this.Entry(auditedEntity).GetDatabaseValues(); 

В результате просто набор значений БД свойств (DbPropertyValues). Если я могу получить исходные значения I установить исходные значения от удаленного объекта:

dbEntityEntry.OriginalValues.SetValues(databaseValues); 

Эта строка просто заполняет исходные значения сущностей, она не изменяет текущее значение на всех. Это полезно сделать так, потому что для проверки каждого свойства и его настройки требуется некоторый код, это интересный ярлык. Теперь проблема в том, что у меня нет сущности для сериализации, поэтому мне нужен новый, который в моем случае я создаю отражением, потому что я не знаю тип (получаю объекты, которые реализуют IAudit)

Type type = auditedEntity.GetType(); 
var auditDeletedEntity = Activator.CreateInstance(type); 

Это сущность, которую я буду сериализовать для последующего хранения аудита.

Теперь сложная часть, мне нужно получить свойства сущности и заполнить их отражением от исходных значений, установленных в объекте:

foreach (var propertyInfo in type.GetProperties()) 
{ 
    if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType) 
    { 
     var propertyValue = originalValues.GetValue<object>(propertyInfo.Name); 
     auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name, 
      BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty, 
      Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue }); 
    } 
} 

я должен был проверить общие и массив типов, чтобы избежать следующих EF отношения, которые не собираются работать с этим методом, и я тоже не нужен (мне нужен объект не все дерево)

После этого я просто нужно сериализовать проверяемом удаленный объект:

strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter()); 

код выглядит следующим образом:

string strJSON = string.Empty; 
if (state == System.Data.Entity.EntityState.Deleted) 
{ 
    var databaseValues = this.Entry(auditedEntity).GetDatabaseValues(); 

    // Get original values from the database (the only option, in the delete method they're lost) 
    DbEntityEntry dbEntityEntry = this.Entry(auditedEntity); 
    if (databaseValues != null) 
    { 
     dbEntityEntry.OriginalValues.SetValues(databaseValues); 
     var originalValues = this.Entry(auditedEntity).OriginalValues; 
     Type type = auditedEntity.GetType(); 
     var auditDeletedEntity = Activator.CreateInstance(type); 
     // Get properties by reflection 
     foreach (var propertyInfo in type.GetProperties()) 
     { 
      if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType) 
      { 
       var propertyValue = originalValues.GetValue<object>(propertyInfo.Name); 
       auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name, 
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty, 
        Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue }); 
      } 
     } 
     strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter()); 
    } 
} 
else 
{ 
    strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter()); 
} 

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