37

Si tengo la siguiente entidad:Entity Framework/SQL2008 - ¿Cómo actualizar automáticamente los campos LastModified para Entidades?

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

que corresponde a una tabla de SQL Server 2008 con el mismo nombre/atributos ...

¿Cómo puedo automáticamente:

  1. Establezca el campo CreatedOn/LastModified para el registro en ahora (al hacer INSERT)
  2. Establecer el campo de última modificación del registro que ahora (cuando se hace ACTUALIZACIÓN)

Cuando digo automáticamente, quiero decir que quiero ser capaz de hacer esto:

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

:

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

Detrás de escena, "algo" debería actualizar automáticamente los campos de fecha y hora. ¿Qué es ese "algo"?

Estoy usando Entity Framework 4.0 - ¿Hay alguna forma en que EF puede hacer eso automáticamente por mí? (Un ajuste especial en el EDMX tal vez?)

Desde el lado de SQL Server, que puede utilizar ValorPredeterminado, pero que sólo funcionará para de INSERT (no ACTUALIZACIÓN 's).

De manera similar, puedo establecer un valor predeterminado usando un constructor en el POCO, pero nuevamente esto solo funcionará al crear una instancia del objeto.

Y, por supuesto, i podría utilizar Disparadores, pero no es ideal.

porque estoy usando Entity Framework, puedo gancho en el SavingChanges evento y actualizar los campos de fecha aquí, pero el problema es que necesito para llegar a ser "consciente" de la POCO de (en este momento, mi repositorio es implementado con genéricos). Tendría que hacer algún tipo de truco OO (como hacer que mi POCO implemente una interfaz, y llamar a un método sobre eso). No estoy en contra de eso, pero si tengo que hacer eso, prefiero configurar manualmente los campos.

Básicamente estoy buscando una solución SQL Server 2008 o Entity Framework 4.0. (o una forma inteligente de .NET)

¿Alguna idea?

EDITAR

Gracias a @marc_s por su respuesta, pero me fui con una solución que es mejor para mi escenario.

+0

simplemente hice esta pregunta de nuevo, pero después de EF6, ya que me encanta esta pregunta y las soluciones ofrecidas, pero los ejemplos de código suministrados no funcionan para EF6.0 como lo están actualmente. – Bart

Respuesta

9

Como tengo una capa de servicio que media entre mis controladores (estoy usando ASP.NET MVC) y mi repositorio, he decidido establecer automáticamente los campos aquí.

Además, mis POCO no tienen relaciones/abstracciones, son completamente independientes. Me gustaría mantenerlo de esta manera, y no marcar ninguna propiedad virtual, ni crear clases base.

así que creé una interfaz, IAutoGenerateDateFields:

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

Por deseo de generar automáticamente estos campos cualquier POCO, i poner en práctica esta inteface.

Utilizando el ejemplo en mi pregunta:

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

En mi capa de servicio, i Ahora compruebe si el objeto concreto implementa la interfaz:

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. 
} 

probablemente voy a romper ese código en el complemento a un asistente/método de extensión más adelante.

Pero creo que esta es una solución decente para mi escenario, ya que no quiero usar virtuales en el Guardar (ya que estoy usando la Unidad de Trabajo, Repositorio y POCO's Puro), y no quiero usar desencadenadores

Si tiene alguna idea/sugerencia, hágamelo saber.

1

tiene dos opciones:

  • tienen una clase base para todas las clases de entidad de negocio que hace el

    poco.LastModified = DateTime.Now; 
    

    en un .Save() método virtual que todos los demás tendrían que llamar

  • utilizar un disparador en la base de datos

No creo que haya otro método razonablemente seguro y fácil de conseguirlo.

+0

No se puede hacer la opción 1. El ahorro ocurre en el UnitOfWork (no en los POCO's - debería haberlo mencionado, lo siento). De ahí que mencione "SavingChanges" como una posibilidad. Realmente no quiero ir por la ruta del gatillo (hace que la depuración sea una pesadilla). Creo que prefiero configurar manualmente los campos en mi capa de servicio. – RPM1984

50

Sé que llego un poco tarde a la fiesta, pero acabo de resolver esto para un proyecto en el que estoy trabajando y pensé en compartir mi solución.

En primer lugar, para que la solución más reutilizable, he creado una clase base con las propiedades de fecha y hora:

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

Entonces me hicieron caso omiso del método SaveChanges en mi 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(); 
    } 
} 
+2

Sí, pensé en esto, pero luego IMO los POCO ya no son "POCO" ya que ahora heredan de una clase base cuyo único propósito es servir las operaciones de marca de tiempo (por ejemplo, una preocupación de la base de datos). – RPM1984

+0

¡gran solución! Agregué este seguimiento en un minuto a mi sitio :) – Gluip

+0

Esto es muy limpio. Una pequeña pregunta sin embargo: ¿cómo se puede agregar en un campo LastModifiedBy? – mpora

6

I también me gustaría presentar una solución tardía. Este solo es aplicable a .NET Framework 4 pero hace que este tipo de tarea sea trivial.

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

¿no es esto básicamente lo mismo que @ Nick's answer? – RPM1984

+1

Este método no requiere una clase base, por lo que la cadena de herencia permanece intacta. – chinupson

0

Podemos utilizar la clase parcial y anular el método SaveChanges para lograr esto.

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(); 
     } 
    } 
} 
4

aquí está la versión editada de la respuesta anterior. El anterior no funcionó para las actualizaciones para mí.

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(); 
    } 
0

tuvo que añadir entidad de base que descienda de mi base de datos de primera clase parcial (que no es lo ideal)

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; 
     } 
    } 
Cuestiones relacionadas