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 значение сейчас (при выполнении ОБНОВЛЕНИЯ)
Когда я говорю автоматически, я хочу, чтобы я мог это сделать:
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 (на данный момент, мой репозиторий реализуется с помощью генериков). Мне нужно было бы сделать какой-то трюк OO (например, сделать мой POCO реализованным интерфейсом и вызвать метод на этом). Я не против этого, но если я должен это сделать, я бы предпочел вручную установить поля.
Я в основном ищу решение для SQL Server 2008 или Entity Framework 4.0. (или умный способ .NET)
Любые идеи?
ИЗМЕНИТЬ
Спасибо @marc_s за его ответ, но я пошел с решением, которое лучше для моего сценария.
Ответы
Ответ 1
Поскольку у меня есть сервисный уровень, посредник между моими контроллерами (im using 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 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) и не хочу использовать триггеры.
Если у вас есть какие-то мысли/предложения, дайте мне знать.
Ответ 2
Я знаю, что немного опоздал на вечеринку, но я просто решил это для проекта, над которым я работаю, и думал, что поделюсь своим решением.
Во-первых, чтобы сделать решение более пригодным для повторного использования, я создал базовый класс со свойствами 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<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();
}
}
Ответ 3
Я также хотел бы предложить последнее решение. Это применимо только к .NET Framework 4, но делает эту задачу тривиальной.
var vs = oceContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var v in vs)
{
dynamic dv = v.Entity;
dv.DateLastEdit = DateTime.Now;
}
Ответ 4
здесь отредактирована версия предыдущего ответа. Предыдущий не работал для обновлений для меня.
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();
}
Ответ 5
У вас есть два варианта:
-
имеют базовый класс для всех классов бизнес-сущностей, который выполняет
poco.LastModified = DateTime.Now;
в виртуальном .Save()
методе, который все остальные должны были бы вызвать
-
использовать триггер в базе данных
Я не думаю, что есть другой разумно безопасный и простой способ достичь этого.
Ответ 6
Для достижения этого мы можем использовать метод partial и переопределить метод 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();
}
}
}
Ответ 7
должен был добавить базовый объект, расширить его из моей первой частичной части базы данных (что не идеально)
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;
}
}