Entity Framework / SQL2008 – Как автоматически обновлять поля LastModified для объектов?

Если у меня есть следующий объект:

public class PocoWithDates { public string PocoName { get; set; } public DateTime CreatedOn { get; set; } public DateTime LastModified { get; set; } } 

Что соответствует таблице SQL Server 2008 с тем же именем / атрибутами …

Как я могу автоматически :

  1. Настройте поле CreatedOn / LastModified для записи (при выполнении INSERT)
  2. Задайте поле LastModified для записи (при выполнении UPDATE)

Когда я говорю автоматически , я имею в виду, что хочу сделать это:

 poco.Name = "Changing the name"; repository.Save(); 

Не это:

 poco.Name = "Changing the name"; poco.LastModified = DateTime.Now; repository.Save(); 

За кулисами «что-то» должно автоматически обновлять поля datetime. Что это за «что-то»?

Я использую Entity Framework 4.0 – есть ли способ, которым EF может сделать это автоматически для меня? (может быть, специальная настройка в EDMX?)

Со стороны SQL Server я могу использовать DefaultValue , но это будет работать только для INSERT (а не для UPDATE).

Аналогично, я могу установить значение по умолчанию, используя конструктор в POCO, но снова это будет работать только при создании объекта.

И, конечно, я мог бы использовать триггеры, но это не идеально.

Поскольку я использую Entity Framework, я могу подключиться к событию SavingChanges и обновить поля даты здесь, но проблема в том, что мне нужно «знать» о POCO (на данный момент мой repository реализуется с помощью дженериков). Мне нужно было бы сделать какой-то трюк OO (например, сделать мой POCO реализованным интерфейсом и вызвать метод на этом). Я не против этого, но если я должен это сделать, я бы предпочел вручную установить поля.

Я в основном ищу решение для SQL Server 2008 или Entity Framework 4.0. (или умный способ .NET)

Есть идеи?

РЕДАКТИРОВАТЬ

Спасибо @marc_s за его ответ, но я пошел с решением, которое лучше для моего сценария.

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

Во-первых, чтобы сделать решение более пригодным для повторного использования, я создал базовый class со свойствами timestamp:

 public class EntityBase { public DateTime? CreatedDate { get; set; } public DateTime? LastModifiedDate { get; set; } } 

Затем я переопределил метод SaveChanges в моем DbContext:

 public class MyContext : DbContext { public override int SaveChanges() { ObjectContext context = ((IObjectContextAdapter)this).ObjectContext; //Find all Entities that are Added/Modified that inherit from my EntityBase IEnumerable objectStateEntries = from e in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified) where e.IsRelationship == false && e.Entity != null && typeof(EntityBase).IsAssignableFrom(e.Entity.GetType()) select e; var currentTime = DateTime.Now; foreach (var entry in objectStateEntries) { var entityBase = entry.Entity as EntityBase; if (entry.State == EntityState.Added) { entityBase.CreatedDate = currentTime; } entityBase.LastModifiedDate = currentTime; } return base.SaveChanges(); } } 

Поскольку у меня есть сервисный уровень, посредничающий между моими controllerами (im using ASP.NET MVC) и мой repository, я решил автоматически установить поля здесь.

Кроме того, у моих POCO нет связей / абстракций, они полностью независимы. Я хотел бы сохранить его таким образом, а не отмечать какие-либо виртуальные свойства или создавать базовые classы.

Итак, я создал интерфейс IAutoGenerateDateFields:

 public interface IAutoGenerateDateFields { DateTime LastModified { get;set; } DateTime CreatedOn { get;set; } } 

Для любых POCO я хочу автоматически генерировать эти поля, я реализую эту интеграцию.

Используя пример в моем вопросе:

 public class PocoWithDates : IAutoGenerateDateFields { public string PocoName { get; set; } public DateTime CreatedOn { get; set; } public DateTime LastModified { get; set; } } 

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

 public void Add(SomePoco poco) { var autoDateFieldsPoco = poco as IAutoGenerateDateFields; // returns null if it's not. if (autoDateFieldsPoco != null) // if it implements interface { autoDateFieldsPoco.LastModified = DateTime.Now; autoDateFieldsPoco.CreatedOn = DateTime.Now; } // ..go on about other persistence work. } 

Я, вероятно, позже перелому этот код в методе добавления в метод помощника / расширения.

Но я считаю, что это достойное решение для моего сценария, поскольку я не хочу использовать виртуальные машины в Save (поскольку я использую Unit of Work, Repository и Pure POCO) и не хочу использовать триггеры.

Если у вас есть какие-либо мысли / предложения, дайте мне знать.

Я также хотел бы предложить последнее решение. Это применимо только к .NET Framework 4, но делает эту задачу тривиальной.

 var vs = oceContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified); foreach (var v in vs) { dynamic dv = v.Entity; dv.DateLastEdit = DateTime.Now; } 

здесь отредактирована версия предыдущего ответа. Предыдущий не работал для обновлений для меня.

 public override int SaveChanges() { var objectStateEntries = ChangeTracker.Entries() .Where(e => e.Entity is TrackedEntityBase && (e.State == EntityState.Modified || e.State == EntityState.Added)).ToList(); var currentTime = DateTime.UtcNow; foreach (var entry in objectStateEntries) { var entityBase = entry.Entity as TrackedEntityBase; if (entityBase == null) continue; if (entry.State == EntityState.Added) { entityBase.CreatedDate = currentTime; } entityBase.LastModifiedDate = currentTime; } return base.SaveChanges(); } 

У вас есть два варианта:

  • имеют базовый class для всех classов бизнес-сущностей, которые

     poco.LastModified = DateTime.Now; 

    в виртуальном .Save() который все остальные должны были бы вызвать

  • использовать триггер в базе данных

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

Для этого мы можем использовать неполный class и переопределить метод SaveChanges.

 using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Core.Objects; using System.Data.Entity.Infrastructure; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web; namespace TestDatamodel { public partial class DiagnosisPrescriptionManagementEntities { public override int SaveChanges() { ObjectContext context = ((IObjectContextAdapter)this).ObjectContext; foreach (ObjectStateEntry entry in (context.ObjectStateManager .GetObjectStateEntries(EntityState.Added | EntityState.Modified))) { if (!entry.IsRelationship) { CurrentValueRecord entryValues = entry.CurrentValues; if (entryValues.GetOrdinal("ModifiedBy") > 0) { HttpContext currentContext = HttpContext.Current; string userId = "nazrul"; DateTime now = DateTime.Now; if (currContext.User.Identity.IsAuthenticated) { if (currentContext .Session["userId"] != null) { userId = (string)currentContext .Session["userId"]; } else { userId = UserAuthentication.GetUserId(currentContext .User.Identity.UserCode); } } if (entry.State == EntityState.Modified) { entryValues.SetString(entryValues.GetOrdinal("ModifiedBy"), userId); entryValues.SetDateTime(entryValues.GetOrdinal("ModifiedDate"), now); } if (entry.State == EntityState.Added) { entryValues.SetString(entryValues.GetOrdinal("CreatedBy"), userId); entryValues.SetDateTime(entryValues.GetOrdinal("CreatedDate"), now); } } } } return base.SaveChanges(); } } } 

должен был добавить базовый объект, расширить его из первой частичной базы данных (что не является идеальным)

  public override int SaveChanges() { AddTimestamps(); return base.SaveChanges(); } public override async Task SaveChangesAsync() { AddTimestamps(); return await base.SaveChangesAsync(); } private void AddTimestamps() { //var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity && (x.State == EntityState.Added || x.State == EntityState.Modified)); //ObjectiveContext context = ((IObjectContextAdapter)this).ObjectContext; var entities = ChangeTracker.Entries().Where(e => e.Entity is BaseEntity && (e.State == EntityState.Modified || e.State == EntityState.Added)).ToList(); var currentUsername = !string.IsNullOrEmpty(System.Web.HttpContext.Current?.User?.Identity?.Name) ? HttpContext.Current.User.Identity.Name : "Anonymous"; foreach (var entity in entities) { if (entity.State == EntityState.Added) { ((BaseEntity)entity.Entity).CREATEDON = DateTime.UtcNow; ((BaseEntity)entity.Entity).CREATEDBY = currentUsername; } ((BaseEntity)entity.Entity).MODIFIEDON = DateTime.UtcNow; ((BaseEntity)entity.Entity).MODIFIEDBY = currentUsername; } } 
  • Как восстановить базу данных в SQL Server?
  • Сохраненная процедура медленна при вызове из Интернета, быстро из Management Studio
  • Как развернуть SQL Server Compact Edition 4.0?
  • Не удается открыть устройство резервного копирования. Ошибка операционной системы 5
  • Правило «Перезапустить компьютер» не удалось выполнить при установке SQL Server 2008
  • Импорт таблиц электронной таблицы Excel в базу данных SQL Server
  • Строки подключения SQL Server - точка (".") Или "(локальная)" или "(localdb)"
  • Как SQL Server сортирует ваши данные?
  • com.microsoft.sqlserver.jdbc.SQLServerException: соединение TCP / IP с локальным хостом хоста, порт 1433 провалилось
  • Производительность bcp / BULK INSERT по сравнению с табличными параметрами
  • Как проверить, существует ли столбец в таблице SQL Server?
  • Давайте будем гением компьютера.