【问题标题】:Entity Framework/SQL2008 - How to Automatically Update LastModified fields for Entities?实体框架/SQL 2008 - 如何自动更新实体的上次修改字段?
【发布时间】:2025-02-23 10:40:02
【问题描述】:

如果我有以下实体:

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

这对应于具有相同名称/属性的 SQL Server 2008 表...

我如何自动

  1. 将记录的 CreatedOn/LastModified 字段设置为 now(执行 INSERT 时)
  2. 将记录的 LastModified 字段设置为 now(进行 UPDATE 时)

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

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's(不适用于 UPDATE's)。

同样,我可以使用 POCO 上的构造函数设置默认值,但同样只有在实例化对象时才有效。

当然我可以使用触发器,但这并不理想。

因为我使用的是实体框架,所以我可以挂钩 SavingChanges 事件并在此处更新日期字段,但问题是我需要“了解” POCO(目前,我的存储库是用泛型实现的)。我需要做一些面向对象的技巧(比如让我的 POCO 实现一个接口,并在上面调用一个方法)。我对此并不反对,但如果我必须这样做,我宁愿手动设置字段。

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

有什么想法吗?

编辑

感谢@marc_s 的回答,但我选择了一个更适合我的场景的解决方案。

【问题讨论】:

  • 我刚刚又问了这个问题,但是对于 EF6,因为我喜欢这个问题和提供的解决方案,但提供的代码示例不适用于目前的 EF6.0。跨度>

标签: c# .net sql-server-2008 entity-framework-4 datetime-generation


【解决方案1】:

我知道我参加聚会有点晚了,但我刚刚为我正在进行的一个项目解决了这个问题,并想分享我的解决方案。

首先,为了使解决方案更具可重用性,我创建了一个具有时间戳属性的基类:

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

然后我覆盖了我的 DbContext 上的 SaveChanges 方法:

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

【讨论】:

  • 是的,我想到了这一点,但后来 IMO 的 POCO 不再是“POCO”,因为它们现在继承自一个基类,其唯一目的是提供时间戳操作(例如数据库问题)。
  • 如果你不想为你的 POCO 使用基类,你可以使用 entry.CurrentValues["CreatedDate"] 来设置值。
  • 非常好。我通过将 EntityBase 的东西移到一个界面中稍微修改了它,并且因为我首先使用数据库,所以我将它添加到生成自定义 DBContext 的 T4 模板中。但非常方便,谢谢。
  • 一定要覆盖SaveChangesAsync。例如。 public override Task&lt;int&gt; SaveChangesAsync() { /* do something */ return base.SaveChangesAsync(); }
  • 上述代码并非在所有情况下都有效。我最终使用不同的逻辑来获取条目: var entries = ChangeTracker.Entries();
【解决方案2】:

由于我的控制器(我使用 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 to a helper/extension 方法中破坏该代码。

但我认为这对于我的场景来说是一个不错的解决方案,因为我不想在 Save 上使用虚拟对象(因为我正在使用 Unit of Work、Repository 和 Pure POCO),并且不想使用触发器.

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

【讨论】:

    【解决方案3】:

    我还想提出一个较晚的解决方案。这个仅适用于 .NET Framework 4,但使此类任务变得微不足道。

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

    【讨论】:

    • 这不是和@Nick的回答基本一样吗?
    • 此方法不需要基类,因此继承链保持不变。
    【解决方案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() 方法中

      • 在数据库中使用触发器

      我认为没有其他合理安全且简单的方法可以实现这一目标。

      【讨论】:

      • 不能执行选项 1。保存发生在 UnitOfWork 上(不是 POCO - 我应该提到这一点,抱歉)。因此我提到了“SavingChanges”作为一种可能性。我真的不想走触发路线(使调试成为一场噩梦)。认为我宁愿手动设置服务层中的字段。
      【解决方案6】:

      必须添加基础实体,从我的数据库第一个部分类扩展它(这并不理想)

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

      【讨论】:

        【解决方案7】:

        我们可以使用部分类并重写 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();
                }
            }
        }
        

        【讨论】:

          最近更新 更多