2016-05-31 3 views
1

Я пытаюсь выполнить «общий» механизм обновления временных данных в моей базе данных SQL Server с использованием Entity Framework.Реализация «универсального» механизма обработки временных данных в Entity Framework

То, что я сделал, это создать «маркерный» интерфейс под названием ITemporalData, который определяет два свойства, которые должны быть представлены - DateTime ValidFrom и DateTime? ValidTo.

public interface ITemporalData 
{ 
    DateTime ValidFrom { get; set; } 
    DateTime? ValidTo { get; set; } 
} 

Я надеялся реализовать «общий» подход в моем DbContext.SaveChanges() переопределение для:

  • клон любой ITemporalData объект, который дал бы мне новый объект для хранения (EntityState.Added), и установите его ValidFrom значение до текущей даты & время
  • сбрасывает исходную, измененную запись в ее значения базы данных (вызывает .Reset() на объекте), а затем устанавливает ValidTo для этой «старой» записи текущая дата & время

В то время как я могу легко отфильтровать измененные ITemporalData объекты в SaveChanges() переопределение, как это:

public partial class MyDbContext 
{ 
    // override the "SaveChanges" method 
    public override int SaveChanges() 
    { 
     DateTime currentDateTime = DateTime.Now; 

     // get the modified entities that implement the ITemporalData interface 
     IEnumerable<DbEntityEntry<ITemporalData>> temporalEntities = ChangeTracker.Entries<ITemporalData>().Where(e => e.State == EntityState.Modified); 

     foreach (var temporalEntity in temporalEntities) 
     { 
      // how would I do that, really? I only have an interface - can't clone an interface...... 
      var cloned = temporalEntity.Entity.Clone(); 

      // and once it's cloned, I would need to add the new record to the correct DbSet<T> to store it 

      // set the "old" records "ValidTo" property to the current date&time 
      temporalEntity.Entity.ValidTo = currentDateTime; 
     } 

     return base.SaveChanges(); 
    } 
} 

Я борющегося с «клонировать измененную запись» подход - I есть только интерфейс ITemporalData, но клонирование (с использованием AutoMapper или других подходов) всегда зависит от фактического, лежащего ниже бетонного типа .....

+0

В чем проблема с клонированием? Вы можете создать новый экземпляр с помощью отражения и скопировать propeties из вашего temporalEntity (снова через отражение) к нему, а не? – Evk

+0

@Evk: Хорошо, так что если это работает (еще не совсем убежден ...) - как добавить этот новый экземпляр в * правильный * 'DbSet' в моем контексте? Как я могу это узнать? –

+0

Вы могли бы использовать триггер db? Я использовал их для автоматического управления версиями. –

ответ

2

Чтобы клонировать объект, вы можете просто создать новый экземпляр с помощью отражения (Activator.CreateInstance) и скопировать все примитивные (неавигационные) свойства с помощью отражения. Лучше не использовать для этого инструменты автоматического сопоставления, так как они также будут получать доступ к свойствам навигации, что может привести к ленивой загрузке (или, по крайней мере, обеспечить, чтобы ленивая загрузка была отключена).

Если вам не нравится отражение (обратите внимание, что авто-картографы будут в любом случае использовать) - вы можете унаследовать интерфейс от ICloneable и реализаций Clone метода для каждого ITemporalData лица (если ваши сущности генерироваться автоматически - использовать частичный класс для что). Затем каждая сущность решает себя, как клонировать, без какого-либо отражения. Этот способ также имеет преимущества в случае, если ваша логика клона сложна (например, включает клонирование связанных объектов из свойств навигации).

Чтобы добавить объект, чтобы исправить DbSet, используйте нетипизированное Set метод DbContext:

this.Set(temporalEntity.GetType()).Add(temporalEntity); 
+0

Это выглядит очень многообещающим - спасибо за ответ! Я исследую еще немного и дам вам знать –

1

Вы можете добавить этот Clone метод в контекст:

T Clone<T>(DbEntityEntry<T> entry) 
    where T : class 
{ 
    var proxyCreationEnabled = this.Configuration.ProxyCreationEnabled; 
    try 
    { 
     this.Configuration.ProxyCreationEnabled = false; 
     var clone = (T)entry.CurrentValues.ToObject(); 
     Set(clone.GetType()).Add(clone); 
     return clone; 
    } 
    finally 
    { 
     this.Configuration.ProxyCreationEnabled = proxyCreationEnabled; 
    } 
} 

И использовать его следующим образом:

var cloned = Clone(temporalEntity); 

clone.GetType wi ll возвращает фактический тип клонированного объекта, тогда как T будет типом времени компиляции, ITemporalData.

Это использует собственную инфраструктуру EF для создания клона, что, без сомнения, быстрее, чем отражение.

Хотя состояние клона сразу установлено на Added, оно не будет выполнять ленивую загрузку. Но может быть безопаснее обеспечить, чтобы клон никогда не был прокси-сервером и, следовательно, никогда не будет запускать ленивую загрузку, если вы решите сделать другие вещи с помощью клона. (Спасибо Евку за его острые комментарии).

+0

Хорошая идея, не знаю о CurrentValues.ToOjbect(). И как это влияет на свойства навигации, когда включена ленивая загрузка? – Evk

+0

@Evk. Хороший вопрос. Только скалярные свойства копируются 'ToObject'. Свойства навигации не загружаются (поскольку клон не является прокси-сервером). Я думаю, что это желаемое поведение, иначе потенциально граф большого объекта будет скопирован (что «интересно», если этот граф содержит другие 'ITemporalData's). –

+0

Интересно, почему клон имеет динамический прокси-тип. Свойства навигации действительно не копируются и не загружаются на клонированный объект, но это тип DynamicProxy .... – Evk