37

如果我有以下实体:实体框架/ 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. 设置上次更改时间字段中记录现在(做UPDATE时)

,当我说自动,我的意思是我希望能够做到这一点:

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

不是这:

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

在幕后,“某事”应该会自动更新日期时间字段。那是什么“东西”?

我正在使用实体框架4.0 - 有没有一种方法,EF可以为我自动做到这一点? (在EDMX也许一个特殊的设置?)

从SQL Server端,我可以使用默认值,但将只对工作INSERT的(未更新的)。

同样,我可以使用POCO的构造函数来设置默认值,但这只会在实例化对象时才起作用。

当然我可能使用触发器,但它并不理想。因为我使用实体框架,我可以挂钩到SavingChanges事件和更新这里的日期字段,但问题是我需要变得“知道”POCO的(目前,我的存储库是用泛型实现)。我需要做一些面向对象的诡计(比如让我的POCO实现一个接口,并且调用一个方法)。我没有被逆转,但如果我必须这样做,我宁愿手动设置字段。

我基本上是在寻找一个SQL Server 2008或Entity Framework 4.0解决方案。 (或一个聪明的.NET方式)

任何想法?

编辑

感谢@marc_s他的回答,但我去了一个解决方案这是我的方案更好。

+0

我刚才问一次这个问题,但后来EF6,因为我很喜欢这个问题,所提供的解决方案,但所提供的代码示例不适用于EF6.0,因为它们是目前的。 – Bart 2014-02-20 15:23:33

回答

9

因为我有一个介于我的控制器(即时通讯使用ASP.NET MVC)和我的存储库之间的服务层,我决定在这里自动设置字段。

此外,我的POCO没有任何关系/抽象,它们是完全独立的。我想保持这种方式,而不是标记任何虚拟属性,或创建基类。

所以我创建了一个接口,IAutoGenerateDateFields:

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

对于任何POCO的我想自动生成这些领域,我实现这个inteface。

使用我的问题的例子:

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上使用虚拟化(因为我使用的是工作单元,存储库和纯POCO),并且不想使用触发。

如果您有任何想法/建议,请告诉我。

1

你有两个选择:

  • 对所有的业务实体类的基类,做了

    poco.LastModified = DateTime.Now; 
    

    在所有其他人将不得不调用

  • 虚拟 .Save()方法
  • 在数据库中使用触发器

我不认为有任何其他合理安全和简单的方法来实现这一点。

+0

不能做选项1.保存发生在UnitOfWork上(不是POCO的 - 我应该提到这一点,对不起)。因此我提到“SavingChanges”是一种可能性。我真的不想去触发路线(使调试噩梦)。想想我宁愿手动设置我的服务层中的字段。 – RPM1984 2010-10-07 08:52:01

50

我知道我对派对有点迟到,但我刚刚解决了这个项目,我正在努力,并认为我会分享我的解决方案。

首先,以使溶液更可重复使用的,我创建与所述时间戳性质的基类:

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

是的,我想到了这一点,但后来海事组织的POCO不再是“POCO”,因为他们现在继承了基类,其唯一目的是为时间戳操作提供服务(例如数据库关注)。 – RPM1984 2011-06-08 22:34:23

+0

伟大的解决方案!在一分钟内将此跟踪添加到我的网站:) – Gluip 2011-10-28 09:55:16

+0

这非常整齐。但有一个小问题:如何添加LastModifiedBy字段? – mpora 2013-02-28 21:54:26

6

我也想提出一个晚点解决方案。这个只适用于.NET Framework 4,但使这种任务变得微不足道。

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

是不是这个和Nick的答案基本一样? – RPM1984 2011-07-25 23:31:21

+1

该方法不需要基类,因此继承链保持不变。 – chinupson 2011-07-26 07:43:54

0

我们可以使用partial 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(); 
     } 
    } 
} 
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(); 
    } 
0

不得不添加基础机构从我的数据库第一部分类扩展它(这是不理想的)

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