Entity Framework / SQL2008 - Как автоматически обновить поля LastModified для сущностей? - PullRequest
41 голосов
/ 07 октября 2010

Если у меня есть следующая сущность:

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 для записи значение сейчас (при выполнении ОБНОВЛЕНИЯ)

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

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 ]

53 голосов
/ 08 июня 2011

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

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

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();
    }
}
9 голосов
/ 12 октября 2010

Поскольку у меня есть сервисный уровень, который является посредником между моими контроллерами (я использую 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 для помощника / расширения.

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

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

6 голосов
/ 02 апреля 2015

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

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();
    }
6 голосов
/ 25 июля 2011

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

1 голос
/ 07 октября 2010

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

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

    poco.LastModified = DateTime.Now;
    

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

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

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

0 голосов
/ 26 мая 2017

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

    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;
        }
    }
0 голосов
/ 18 сентября 2014

Мы можем использовать частичный класс и переопределить метод 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();
        }
    }
}
...