实体框架/ SQL2008 - 如何自动更新实体的LastModified字段?

RPM*_*984 41 .net c# sql-server-2008 entity-framework-4 datetime-generation

如果我有以下实体:

public class PocoWithDates
{
   public string PocoName { get; set; }
   public DateTime CreatedOn { get; set; }
   public DateTime LastModified { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

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

我怎么能自动:

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

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

poco.Name = "Changing the name";
repository.Save(); 
Run Code Online (Sandbox Code Playgroud)

不是这个:

poco.Name = "Changing the name";
poco.LastModified = DateTime.Now;
repository.Save();
Run Code Online (Sandbox Code Playgroud)

在幕后,"某事"应该自动更新日期时间字段.什么是"东西"?

我正在使用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的答案,但我选择了一个更适合我的方案的解决方案.

Nic*_*ick 53

我知道我参加聚会的时间有点晚了,但我刚刚解决了这个项目,我正在研究并认为我会分享我的解决方案.

首先,为了使解决方案更易于重用,我创建了一个带有timestamp属性的基类:

public class EntityBase
{
    public DateTime? CreatedDate { get; set; }
    public DateTime? LastModifiedDate { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后我覆盖了我的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 上面的代码并不适用于所有情况.我结束使用不同的逻辑来获取条目:var entries = ChangeTracker.Entries(); (6认同)
  • 如果您不想为POCO使用基类,可以使用entry.CurrentValues ["CreatedDate"]来设置值. (3认同)
  • 是的,我想到了这一点,但是然后IMO POCO不再是"POCO",因为它们现在继承自一个基类,它的唯一目的是服务时间戳操作(例如数据库问题). (2认同)
  • 一定要覆盖`SaveChangesAsync`.例如`public override Task <int> SaveChangesAsync(){/*做某事*/return base.SaveChangesAsync(); }` (2认同)

RPM*_*984 9

因为我有一个服务层在我的控制器(我使用ASP.NET MVC)和我的存储库之间进行调解,所以我决定在这里自动设置字段.

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

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

public interface IAutoGenerateDateFields
{
   DateTime LastModified { get;set; }
   DateTime CreatedOn { get;set; }
}
Run Code Online (Sandbox Code Playgroud)

对于任何POCO我希望自动生成这些字段,我实现这个接口.

使用我的问题中的示例:

public class PocoWithDates : IAutoGenerateDateFields
{
   public string PocoName { get; set; }
   public DateTime CreatedOn { get; set; }
   public DateTime LastModified { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

在我的服务层,我现在检查具体对象是否实现了接口:

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.
}
Run Code Online (Sandbox Code Playgroud)

我稍后可能会在Add to a helper/extension方法中破坏该代码.

但我认为这对我的场景来说是一个不错的解决方案,因为我不想在Save上使用虚拟(因为我使用的是工作单元,存储库和Pure POCO),并且不想使用触发器.

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


chi*_*son 6

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

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

  • 这不是@ Nick的回答基本相同吗? (2认同)

ser*_*alo 6

这是以前回复的编辑版本.以前的一个不适合我的更新.

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();
    }
Run Code Online (Sandbox Code Playgroud)