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 с тем же именем / атрибутами...
Как я могу автоматически:
- Установите поле CreatedOn/LastModified для записи сейчас (при выполнении INSERT)
- Установите поле LastModified для записи сейчас (при выполнении UPDATE)
Когда я говорю автоматически, я имею в виду, что хочу сделать это:
poco.Name = "Changing the name";
repository.Save();
Не это:
poco.Name = "Changing the name";
poco.LastModified = DateTime.Now;
repository.Save();
За кулисами "что-то" должно автоматически обновлять поля даты и времени. Что это за "что-то"?
Я использую Entity Framework 4.0 - есть ли способ, что EF может сделать это автоматически для меня? (специальная настройка в EDMX может быть?)
Со стороны SQL Server я могу использовать DefaultValue, но это будет работать только для INSERT (не UPDATE).
Точно так же я могу установить значение по умолчанию, используя конструктор в POCO, но опять же это будет работать только при создании экземпляра объекта.
И, конечно, я мог бы использовать триггеры, но это не идеально.
Поскольку я использую Entity Framework, я могу подключиться к событию SavingChanges и обновить поля даты здесь, но проблема в том, что мне нужно "осознать" POCO (на данный момент мой репозиторий реализован с использованием обобщений). Мне нужно было бы сделать какую-то хитрость OO (например, заставить мой POCO реализовать интерфейс и вызвать метод для этого). Я не против этого, но если мне придется сделать это, я бы предпочел вручную установить поля.
Я в основном ищу решение SQL Server 2008 или Entity Framework 4.0. (или умный способ.NET)
Есть идеи?
РЕДАКТИРОВАТЬ
Спасибо @marc_s за его ответ, но я выбрал решение, которое лучше для моего сценария.
7 ответов
Поскольку у меня есть сервисный уровень, который является посредником между моими контроллерами (я использую ASP.NET MVC) и моим хранилищем, я решил автоматически установить поля здесь.
Кроме того, мои POCO не имеют отношений / абстракций, они абсолютно независимы. Я хотел бы сохранить это, и не отмечать какие-либо виртуальные свойства, или создавать базовые классы.
Поэтому я создал интерфейс, 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.
}
Вероятно, позже я сломаю этот код в методе Add out to helper/extension.
Но я думаю, что это достойное решение для моего сценария, так как я не хочу использовать виртуалы в Save (так как я использую Unit of Work, Repository и Pure POCO) и не хочу использовать триггеры.
Если у вас есть какие-либо мысли / предложения, дайте мне знать.
Я знаю, что немного опоздал на вечеринку, но я решил это для проекта, над которым я работаю, и решил поделиться своим решением.
Во-первых, чтобы сделать решение более пригодным для повторного использования, я создал базовый класс со свойствами метки времени:
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<ObjectStateEntry> 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();
}
}
Я также хотел бы предложить позднее решение. Это применимо только к.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();
}
Пришлось добавить базовую сущность расширить ее из моей базы данных первого частичного класса (что не является идеальным)
public override int SaveChanges()
{
AddTimestamps();
return base.SaveChanges();
}
public override async Task<int> 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;
}
}
У вас есть два варианта:
иметь базовый класс для всех ваших классов бизнес-сущностей, который делает
poco.LastModified = DateTime.Now;
в виртуальном
.Save()
метод, который должны вызывать все остальныеиспользовать триггер в базе данных
Я не думаю, что есть какой-либо другой достаточно безопасный и простой способ добиться этого.
Мы можем использовать частичный класс и переопределить метод 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();
}
}
}