【问题标题】:How to automatically populate CreatedDate and ModifiedDate?如何自动填充 CreatedDate 和 ModifiedDate?
【发布时间】:2016-11-06 02:03:35
【问题描述】:

我正在学习 ASP.NET Core MVC,我的模型是

namespace Joukyuu.Models
{
    public class Passage
    {
        public int PassageId { get; set; }
        public string Contents { get; set; }


        public DateTime CreatedDate { get; set; }
        public DateTime ModifiedDate { get; set; }
    }
}

Passage 表用于保存我写的段落。

场景

  • Create 视图只有一个字段Contents 用于输入段落。 CreatedDateModifiedDate 必须由服务器自动设置为相等(使用 UTC 格式)。

  • Edit 视图只有一个字段Contents 用于编辑段落。 ModifiedDate 必须由服务器自动设置。

问题

我必须将哪些属性附加到CreatedDateModifiedDate 属性才能使它们根据上述情况由服务器自动填充?

【问题讨论】:

标签: c# entity-framework asp.net-core-mvc


【解决方案1】:

我必须将哪些属性附加到 CreatedDate 和 ModifiedDate 属性以使它们根据上述情况由服务器自动填充?

解决方案 1)

namespace Joukyuu.Models
{
    public class Passage
    {
        public int PassageId { get; set; }
        public string Contents { get; set; }


        public DateTime CreatedDate { get; set; }
        public DateTime ModifiedDate { get; set; }

       public Passage()
       {          
         this.CreatedDate  = DateTime.UtcNow;
         this.ModifiedDate = DateTime.UtcNow;
       }
    }
}

通过编辑,您必须自己更改/更新它!

解决方案 2)

自定义属性:

[SqlDefaultValue(DefaultValue = "getutcdate()")]
public DateTime CreatedDate { get; set; }

Entity Framework 6 Code first Default value

解决方案 3)

在计算的帮助下:

[Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedUtc { get; set; 


  "dbo.Products",
            c => new
                {
                    ProductId = c.Int(nullable: false, identity: true),
                    Name = c.String(),
                    CreatedUtc = c.DateTime(nullable: false, defaultValueSql: "GETUTCDATE()"),
                })
            .PrimaryKey(t => t.ProductId);

https://andy.mehalick.com/2014/02/06/ef6-adding-a-created-datetime-column-automatically-with-code-first-migrations/

解决方案 4) 您也可以通过手动修改查询来使用命令拦截器来做到这一点。

解决方案 5) 使用 Repository 模式管理数据创建并由 CreateNew 设置 这是我喜欢的解决方案!

https://msdn.microsoft.com/en-us/library/ff649690.aspx

解决方案 6) 只需在 UI 或 VM 中设置或进入。


Entity Framework Core 1.0中很容易:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Passage>()
        .Property(b => b.CreatedDate )
        .HasDefaultValueSql("getdate()");
}

【讨论】:

  • 我们可以在Contents setter 中插入ModifiedDate=DateTime.UtcNow(); 吗?
  • 不,这不是一个好主意,因为 Entity Framework 稍后将使用 setter 来填充(物化数据 = 从 SQL 到实体的行)。这意味着您将始终获取当前设置的数据,而不是从数据库中获取。
  • 关于你的 EF Core 1 代码(在最底部),你为什么不用getutcdate() 而不是getdate()
  • HasDefaultValueSql() 在什么命名空间/包中?
  • 遗憾的是,99% 的在线解决方案都提出了一种将代码与特定数据库引擎联系起来的方法。 .HasValueGenerator() 属性允许使用 .Net 代码作为初始化程序。它并不适合所有情况,但大多数情况下都可以。
【解决方案2】:

对于使用异步系统(SaveChangesAsync)和.NET Core的用户,最好重写DbContextSaveChangesAsync方法:

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

【讨论】:

  • 我认为这会影响性能。每次,它都会迭代所有实体。
  • 不,它是异步的
  • @MukeshKumar 它迭代了保存和修改的条目是的。除非你在一个请求中保存了数千个条目,否则你很好(即使那样也很好..)。这是一个很好的答案。 -Arash 异步方法并不神奇。它们仍然会影响性能..
  • 能否请您详细说明为什么这是一种更好的方法?
  • @GuillermoPrandi 您应该能够设置模型配置以让您的 DBMS 为您执行此操作,性能更高。
【解决方案3】:

如果您使用的是代码优先,您可以试试这个

[Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedDate { get; set; }

关于迁移

AddColumn("Passage", "CreatedDate", n => n.DateTime(nullable: false, defaultValueSql: "GETDATE()"));

更多参考here,similar answer

或者您可以全局覆盖saveChanges 注意*如果您有CreatedDate 字段,这将影响整个模型

public override int SaveChanges()
{
  DateTime saveTime = DateTime.Now;
  foreach (var entry in this.ChangeTracker.Entries()
      .Where(e => e.State == (EntityState) System.Data.EntityState.Added))
   {
     if (entry.Property("CreatedDate").CurrentValue == null)
       entry.Property("CreatedDate").CurrentValue = saveTime;
   }
   return base.SaveChanges();  
}

【讨论】:

    【解决方案4】:

    PostgreSQL中CreationDate的解决方案:

    builder.Property(e => e.CreationDate)
      .HasColumnType("timestamp without time zone")
      .HasDefaultValueSql("NOW()")
      .ValueGeneratedOnAdd();
    

    通过:https://www.npgsql.org/efcore/modeling/generated-properties.html#guiduuid-generation 不幸的是没有更新事件的解决方案

    【讨论】:

    • 您的答案中的链接目前已损坏,我认为这就是您的意思:npgsql.org/efcore/modeling/… 此外,您的示例未显示计算列,只是具有默认值的普通列。但是你的权利 OnUpdate 的等效代码似乎不起作用。
    【解决方案5】:

    使用 SaveChangesAsync 的替代方法是您可以为相应的数据库创建数据库触发器。我为 mssql、postgress、sqlite 和 mysql 制作了一个带有一些辅助函数的 gist。这是代码的粘贴。

    有一个有趣的讨论here 具有数据库逻辑与应用程序逻辑。就个人而言,让数据库强制执行 updated_date 列更新在我的代码中的行数更少。他们还提出的一个论点是,如果使用不同的服务更改了您的数据库,这些数据库完整性约束仍将适用于数据库级别。

    每当我进行核心数据迁移以在表和列更新上添加自定义更新列触发器时,我一直在使用下面的这个类。

    using Microsoft.EntityFrameworkCore.Metadata;
    using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
    
    namespace ProjectNamespace.Api.Utils
    {
        public class DatabaseTools
        {
            public class DatabaseKeys
            {
                public string ValueGenerationStrategy;
                public object SerialColumn;
                public string DEFAULTDATE_CREATE;
                public string DEFAULTDATE_UPDATE;
                public string dateTime;
                public bool UpdateDateTrigger = true;
                public string ProviderName { get; set; }
            }
            /*
            Notes:
            can check if updateDateTrigger needed for dataprovider and add update triggers
            if (dbKeys.UpdateDateTrigger)
                {
                    migrationBuilder.Sql(DatabaseTools.getUpdateDateTrigger(dbKeys.ProviderName, "TG_roles_updated_at", "roles", "updated_at"));
                }
            if postgress don't forget to drop the generated functions for each of the different named update columns (updated_at, modified_date) at the end of the migration E.g.
            protected override void Down(MigrationBuilder migrationBuilder){...
            if (dbKeys.ProviderName == "postgress")
                {
                    migrationBuilder.Sql(@"
                        DROP FUNCTION update_" + "updated_at" + @"_column();
                        ");
                }
            postgres doesn't support parameters in triggers for new and old keywords.
             */
            /// <summary>
            /// needed for non mysql implementation to update a datetime on a record update
            /// </summary>
            /// <param name="databaseProvider"></param>
            /// <param name="name"></param>
            /// <param name="table"></param>
            /// <param name="column"></param>
            /// <param name="schema"></param>
            /// <param name="id_column"></param>
            /// <returns></returns>
            public static string getUpdateDateTrigger(string databaseProvider, string name, string table, string column, string schema = "", string id_column = "id")
            {
                string updateDateTrigger = null;
                if (!string.IsNullOrEmpty(schema))
                    schema = schema + ".";
                switch (databaseProvider)
                {
                    case "sqlite":
                        updateDateTrigger = @"
                        CREATE TRIGGER [" + name + @"]  
                            AFTER   
                            UPDATE  
                            ON " + table + @"
                            FOR EACH ROW   
                            WHEN NEW." + column + @" <= OLD." + column + @"  
                        BEGIN  
                            update " + table + @" set " + column + @"=CURRENT_TIMESTAMP where " + id_column + @"=OLD." + id_column + @";  
                        END  
                        ";
                        break;
                    case "postgress":
                        updateDateTrigger = @"
                        CREATE OR REPLACE FUNCTION update_" + column + @"_column() RETURNS TRIGGER AS
                        $$
                        BEGIN
                            NEW.""" + column + @""" = now();
                            RETURN NEW;  
                        END;
                        $$ LANGUAGE plpgsql;
                        CREATE TRIGGER " + name + @" 
                        BEFORE UPDATE ON " + schema + @"""" + table + @"""
                        FOR EACH ROW EXECUTE PROCEDURE update_" + column + @"_column();
                        ";
                        break;
                    case "sqlserver":
                        updateDateTrigger = @"
                        CREATE TRIGGER " + name + @" 
                        ON " + table + @"
                        AFTER UPDATE AS
                            UPDATE " + table + @"
                            SET " + column + @" = GETDATE()
                            WHERE " + id_column + @" IN (SELECT DISTINCT " + id_column + @" FROM Inserted);
                        ";
                        break;
                }
    
                return updateDateTrigger;
            }
            /*
            Notes:
            if in OnModelCreating can call as var dbKeys = DatabaseTools.getDatabaseDefaults(this.Database.ProviderName);
            if in a migration call as var dbKeys = DatabaseTools.getDatabaseDefaults(migrationBuilder.ActiveProvider);
            Then update any datetime columns for correct defaults
            created_at = table.Column<DateTime>(type: dbKeys.dateTime, nullable: true, defaultValueSql: dbKeys.DEFAULTDATE_CREATE),
            updated_at = table.Column<DateTime>(type: dbKeys.dateTime, nullable: true, defaultValueSql: dbKeys.DEFAULTDATE_UPDATE)
             */
            /// <summary>
            /// Can get specific formats for different databases. Supports mssql, postgress, sqlite, and mysql.
            /// </summary>
            /// <param name="databaseProvider"></param>
            /// <returns></returns>
            public static DatabaseKeys getDatabaseDefaults(string databaseProvider)
            {
                var dbKeys = new DatabaseKeys();
                dbKeys.dateTime = "datetime";
    
                switch (databaseProvider)
                {
                    case "Microsoft.EntityFrameworkCore.Sqlite":
                    case "sqlite":
                        dbKeys.DEFAULTDATE_UPDATE = "datetime('now')";
                        dbKeys.SerialColumn = true;
                        dbKeys.ProviderName = "sqlite";
                        dbKeys.ValueGenerationStrategy = "Sqlite:Autoincrement";
                        break;
                    case "postgress":
                    case "Npgsql.EntityFrameworkCore.PostgreSQL":
                        dbKeys.DEFAULTDATE_UPDATE = "CURRENT_TIMESTAMP";
                        dbKeys.SerialColumn = NpgsqlValueGenerationStrategy.SerialColumn;
                        dbKeys.ProviderName = "postgress";
                        dbKeys.ValueGenerationStrategy = "Npgsql:ValueGenerationStrategy";
                        dbKeys.dateTime = "timestamp";
                        break;
                    case "mysql":
                    case "MySql.Data.EntityFrameworkCore":
                        dbKeys.DEFAULTDATE_UPDATE = "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP";
                        dbKeys.DEFAULTDATE_CREATE = "CURRENT_TIMESTAMP";
                        dbKeys.UpdateDateTrigger = false;
                        dbKeys.SerialColumn = true;
                        dbKeys.ProviderName = "mysql";
                        dbKeys.ValueGenerationStrategy = "MySQL:AutoIncrement";
                        break;
                    case "sqlserver":
                    default:
                        dbKeys.DEFAULTDATE_UPDATE = "(getdate())";
                        dbKeys.SerialColumn = SqlServerValueGenerationStrategy.IdentityColumn;
                        dbKeys.ProviderName = "sqlserver";
                        dbKeys.ValueGenerationStrategy = "SqlServer:ValueGenerationStrategy";
                        break;
                }
                if (string.IsNullOrEmpty(dbKeys.DEFAULTDATE_CREATE))
                    dbKeys.DEFAULTDATE_CREATE = dbKeys.DEFAULTDATE_UPDATE;
                return dbKeys;
            }
        }
    }
    

    【讨论】:

    • 将触发器添加到数据库被认为是不好的做法,因为它将代码移动到数据库并且在应用程序中并不明显。
    • @cpoDesign 我不同意。我考虑了这些更多的数据库约束,它们通过使用这些一致的触发器使维护和扩展代码更容易,而不必记住设置更新的列值或执行一些全局应用程序逻辑来更新值。
    【解决方案6】:

    太简单了,只需两步:

    1.在createDate 字段之上创建具有这些字段的模型:

     [Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    

    2.创建迁移后,在其文件中更新数据库前编辑这部分的列,并添加defaultValueSql: "getdate()",如下所示:

     id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    CreatedDate = table.Column<DateTime>(defaultValueSql: "getdate()",nullable: false)
       
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-12-14
      • 2011-03-02
      • 2020-05-29
      • 1970-01-01
      • 1970-01-01
      • 2012-04-02
      • 2021-12-18
      • 2021-04-06
      相关资源
      最近更新 更多