【问题标题】:How to set created date and Modified Date to enitites in DB first approach如何在 DB first 方法中为实体设置创建日期和修改日期
【发布时间】:2016-09-14 03:12:23
【问题描述】:

在我们应用程序的每个 SQL 表中,我们都有“CreatedDate”和“ModifiedDate”列。我们正在使用 DB 优先方法。当我保存数据时,我希望这两列自动填充。一种方法是在 SQL 表列本身上将默认值设置为 getdate()。所以这将部分解决问题。这意味着它将在实体为新实体时设置 CreatedDate 和 ModifiedDate。 但是,当我编辑/更新实体时,我只想更新 ModifiedDate
有很多文章使用代码优先的方法来做这件事。但我们使用的是数据库优先方法。
我在这里有什么选择?

【问题讨论】:

标签: entity-framework ef-database-first


【解决方案1】:

您可以覆盖 DbContext 中的 SaveChanges 方法并获取已添加和已修改您的实体的列表,如果已添加则设置 CreatedDate,如果已修改则设置 ModifiedDate。

public override int SaveChanges()
{
    var AddedEntities = ChangeTracker.Entries<Entity>().Where(E => E.State == EntityState.Added).ToList();

    AddedEntities.ForEach(E => 
    {
        E.CreatedDate = DateTime.Now;
    });

    var ModifiedEntities = ChangeTracker.Entries<Entity>().Where(E => E.State == EntityState.Modified).ToList();

    ModifiedEntities.ForEach(E => 
    {
        E.ModifiedDate = DateTime.Now;
    });

    return base.SaveChanges();
}

您还可以编写一个具有两个 DateTime 属性的接口并让您的实体继承它们。

interface IEntityDate
{
    DateTime AddedDate { set; get;}
    DateTime ModifiedDate { set; get;}
}

class Entity : IEntityDate
{
    public DateTime AddedDate { set; get;}
    public DateTime ModifiedDate { set; get;}
}

【讨论】:

  • 井实体由 T4 创建。这些实体不是从任何接口派生的。所以我想我必须修改那个 T4,对吧?
  • @LP13 我的错!如果您先使用 Db,则不能使用接口。我认为唯一的解决方案是在数据库中添加这两列,然后更新模型
  • 小问题,但我也建议使用 DateTime.UtcNow 存储 CreatedDate 和 ModifedDate 以避免处理时区问题。
【解决方案2】:

对于那些使用异步系统 (SaveChangesAsync) 和 .net core 的人最好将以下代码添加到 DbContext 类中。

    public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
    {
        var AddedEntities = ChangeTracker.Entries().Where(E => E.State == EntityState.Added).ToList();

        AddedEntities.ForEach(E =>
        {
            E.Property("CreationTime").CurrentValue = DateTime.Now;
        });

        var EditedEntities = ChangeTracker.Entries().Where(E => E.State == EntityState.Modified).ToList();

        EditedEntities.ForEach(E =>
        {
            E.Property("ModifiedDate").CurrentValue = DateTime.Now;
        });

        return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
    }

你也可以像下面这样定义一个类或接口。

public class SaveConfig
{
    public DateTime CreationTime { get; set; }
    public DateTime? ModifiedDate { get; set; }
}

只需从 SaveConfig 实体继承实体。

【讨论】:

  • 完美,谢谢!此外,如果您确实SaveConfig 继承,您可以使用ChangeTracker.Entries&lt;SaveConfig&gt;()...,然后将E.Property("CreationTime").CurrentValue 替换为E.Entity.CreationTime,避免使用魔法字符串:)
  • 这就像一个魅力。我用你的代码覆盖SaveChangesAsync()this code覆盖SaveChanges()
【解决方案3】:

我做了与@Arash 提出的公共覆盖“Task SaveChangesAsync”类似的事情,不同之处在于我的设置我只想更新实际具有 CreatedDate/ModifiedDate 属性的实体,这就是我想出的和。与@Arash 非常相似,但也许它可以帮助某人。 添加了几个常量 EntityCreatedPropertyName 和 EntityModifiedPropertyName 来定义属性的名称,这样我就不必在我使用它们的地方使用重复的字符串。

    public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
    {
        #region Automatically set "CreatedDate" property on an a new entity
        var AddedEntities = ChangeTracker.Entries().Where(E => E.State == EntityState.Added).ToList();

        AddedEntities.ForEach(E =>
        {
            var prop = E.Metadata.FindProperty(EntityCreatedPropertyName);
            if (prop != null)
            {
                E.Property(EntityCreatedPropertyName).CurrentValue = DateTime.Now;
            }
        });
        #endregion

        #region Automatically set "ModifiedDate" property on an a new entity
        var EditedEntities = ChangeTracker.Entries().Where(E => E.State == EntityState.Modified).ToList();

        EditedEntities.ForEach(E =>
        {
            var prop = E.Metadata.FindProperty(EntityModifiedPropertyName);
            if (prop != null)
            {
                E.Property(EntityModifiedPropertyName).CurrentValue = DateTime.Now;
            }
        });
        #endregion

        return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
    }

【讨论】:

    【解决方案4】:

    如果您想覆盖 OnSave,您必须覆盖所有保存方法。在 EF 核心 2.1 中,您可以使用带有 ChangeTracked 和事件的更好的解决方案。例如:

    您可以创建接口或基类,如下例所示:

    public interface IUpdateable 
    { 
        DateTime ModificationDate{ get; set; }
    }
    
    public class SampleEntry : IUpdateable
    {
        public int Id { get; set; }
        public DateTime ModificationDate { get; set; }
    } 
    

    然后在创建上下文时将事件添加到更改跟踪器:

    context.ChangeTracker.StateChanged += context.ChangeTracker_StateChanged;
    

    和方法:

    private void ChangeTracker_StateChanged(object sender, Microsoft.EntityFrameworkCore.ChangeTracking.EntityStateChangedEventArgs e)
        {
            if(e.Entry.Entity is IUpdateable && e.Entry.State == EntityState.Modified)
            {
                var entry = ((IUpdateable)e.Entry.Entity);
                entry.ModificationDate = DateTime.Now;
            }
        }
    

    这更简单,您不必重写所有方法。

    【讨论】:

    【解决方案5】:

    这是我在 EF Core 2.1 中解决的方法:

    我有一个基础模型,它继承了一个类似的接口:

    public interface IAuditableModel
    {
        DateTime Created { get; }
        DateTime? Updated { get; set; }
    }
    
    public class BaseModel : IAuditableModel
    {
        public BaseModel()
        {
        }
    
        public int Id { get; set; }
    
        public DateTime Created { get; private set; }
    
        public DateTime? Updated { get; set; }
    }
    

    然后,在我的 DbContext 中:

    public override int SaveChanges()
        {
            var added = ChangeTracker.Entries<IAuditableModel>().Where(E => E.State == EntityState.Added).ToList();
    
            added.ForEach(E =>
            {
                E.Property(x => x.Created).CurrentValue = DateTime.UtcNow;
                E.Property(x => x.Created).IsModified = true;
            });
    
            var modified = ChangeTracker.Entries<IAuditableModel>().Where(E => E.State == EntityState.Modified).ToList();
    
            modified.ForEach(E =>
            {
                E.Property(x => x.Updated).CurrentValue = DateTime.UtcNow;
                E.Property(x => x.Updated).IsModified = true;
    
                E.Property(x => x.Created).CurrentValue = E.Property(x => x.Created).OriginalValue;
                E.Property(x => x.Created).IsModified = false;
            });
    
            return base.SaveChanges();
        }
    

    这使您可以避免对任何属性名称进行硬编码,并让您可以灵活地将特定接口应用于您选择的模型。就我而言,我希望在大多数模型上都使用它,因此我将其应用于所有其他模型继承自的 BaseModel。

    此外,我在编辑这些模型的视图中发现,创建的时间戳被删除了,因为我没有将它加载到页面上。结果,当模型在回发时绑定时,该属性被删除,有效地将我创建的时间戳设置为 0。

    【讨论】:

      【解决方案6】:

      查看.NET Core的这个答案:

      https://stackoverflow.com/a/64824067/3850405

      我喜欢这样使用更通用的方法:

      interface IEntityDate
      {
          DateTime CreatedDate { get; set; }
      
          DateTime UpdatedDate { get; set; }
      }
      
      public abstract class EntityBase<T1>: IEntityDate
      {
          public T1 Id { get; set; }
      
          public virtual DateTime CreatedDate { get; set; }
          public virtual string CreatedBy { get; set; }
          public virtual DateTime UpdatedDate { get; set; }
          public virtual string UpdatedBy { get; set; }
      }
      
      public override int SaveChanges()
      {
          var now = DateTime.UtcNow;
      
          foreach (var changedEntity in ChangeTracker.Entries())
          {
              if (changedEntity.Entity is IEntityDate entity)
              {
                  switch (changedEntity.State)
                  {
                      case EntityState.Added:
                          entity.CreatedDate = now;
                          entity.UpdatedDate = now;
                          break;
      
                      case EntityState.Modified:
                          Entry(entity).Property(x => x.CreatedDate).IsModified = false;
                          entity.UpdatedDate = now;
                          break;
                  }
              }
          }
      
          return base.SaveChanges();
      }
      

      更新:

      为了处理 CreatedByUpdatedBy,我使用 DbContext 的包装器,如下所示:

      public interface IEntity
      {
          DateTime CreatedDate { get; set; }
      
          string CreatedBy { get; set; }
      
          DateTime UpdatedDate { get; set; }
      
          string UpdatedBy { get; set; }
      }
      
      public interface ICurrentUser
      {
          string GetUsername();
      }
      
      public class ApplicationDbContextUserWrapper
      {
          public ApplicationDbContext Context;
      
          public ApplicationDbContextUserWrapper(ApplicationDbContext context, ICurrentUser currentUser)
          {
              context.CurrentUser = currentUser;
              this.Context = context;
          }
      }
      
      public class ApplicationDbContext : DbContext
      {
      
          public ICurrentUser CurrentUser;
          
          public override int SaveChanges()
          {
              var now = DateTime.UtcNow;
      
              foreach (var changedEntity in ChangeTracker.Entries())
              {
                  if (changedEntity.Entity is IEntity entity)
                  {
                      switch (changedEntity.State)
                      {
                          case EntityState.Added:
                              entity.CreatedDate = now;
                              entity.UpdatedDate = now;
                              entity.CreatedBy = CurrentUser.GetUsername();
                              entity.UpdatedBy = CurrentUser.GetUsername();
                              break;
                          case EntityState.Modified:
                              Entry(entity).Property(x => x.CreatedBy).IsModified = false;
                              Entry(entity).Property(x => x.CreatedDate).IsModified = false;
                              entity.UpdatedDate = now;
                              entity.UpdatedBy = CurrentUser.GetUsername();
                              break;
                      }
                  }
              }
      
              return base.SaveChanges();
          }
          
          ...
      

      【讨论】:

      • 你设置得怎么样了,UpdateBy?
      • @IvanSalo 用我如何解决这个问题更新了我的答案。
      • 我想覆盖自动创建并想在控制器中手动设置它怎么样
      • 哇,这对我来说太棒了!!非常感谢@Ogglas!
      • 问题是数据库优先方法。这不适用于 Database First,因为会生成上下文文件。
      【解决方案7】:

      这些答案已经过时了。 2022年,我们可以使用:

      [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
      public DateTime created { get; set; }
      
      [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
      public DateTime updated { get; set; }
      

      https://docs.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations#default-values

      对于之前删除我答案的版主(@Samuel Liew),请不要在不知道您在说什么的情况下删除内容。您正在删除截至 2022 年在搜索引擎中排名靠前的问题的唯一正确答案。

      【讨论】:

        猜你喜欢
        • 2018-06-11
        • 1970-01-01
        • 1970-01-01
        • 2023-02-15
        • 1970-01-01
        • 1970-01-01
        • 2014-06-08
        • 2015-08-08
        • 1970-01-01
        相关资源
        最近更新 更多