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