You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何正确使用ValueGeneratedOnUpdate()?EF Core自动更新时间戳咨询

我来帮你搞定这个更新时间戳的问题!你现在用EF Core 2.1.0-preview1-final的Code First模式搭配SQL Server 2014,插入记录时InsertDateTimeUtc能自动填充UTC时间,但UpdateDateTimeUtc在更新时没动静,初始还是0001-01-01对吧?下面给你两种靠谱的解决方案,你可以根据需求选:

方案一:在EF层面自动处理(无需数据库触发器)

这种方式是通过重写DbContext的保存方法,在保存变更时自动给更新的实体设置最新UTC时间,适合不想折腾数据库触发器的场景,缺点是如果直接用SQL语句修改数据库数据,更新时间不会自动更新。

步骤1:给实体类加统一接口(可选但更通用)

先定义一个接口,让需要更新时间戳的实体都实现它:

public interface IHasUpdateTimestamp
{
    DateTime UpdateDateTimeUtc { get; set; }
}

然后修改你的Person类实现这个接口:

public class Person : IHasUpdateTimestamp
{
    // 假设你的主键是Id,这里补上
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime InsertDateTimeUtc { get; set; }
    public DateTime UpdateDateTimeUtc { get; set; }
}

步骤2:重写DbContext的SaveChanges方法

在你的DbContext类里添加以下代码,自动处理更新时间:

public override int SaveChanges()
{
    UpdateEntityTimestamps();
    return base.SaveChanges();
}

public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    UpdateEntityTimestamps();
    return await base.SaveChangesAsync(cancellationToken);
}

private void UpdateEntityTimestamps()
{
    // 找出所有状态为Modified的、实现了IHasUpdateTimestamp的实体
    var modifiedEntities = ChangeTracker.Entries()
        .Where(e => e.Entity is IHasUpdateTimestamp && e.State == EntityState.Modified);

    foreach (var entry in modifiedEntities)
    {
        // 设置最新的UTC时间
        ((IHasUpdateTimestamp)entry.Entity).UpdateDateTimeUtc = DateTime.UtcNow;
    }
}

步骤3:配置实体属性

在DbContext的OnModelCreating方法里,给两个时间字段做配置,确保插入时UpdateDateTimeUtc也有初始值:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .Property(p => p.InsertDateTimeUtc)
        .ValueGeneratedOnAdd()
        .HasDefaultValueSql("GETUTCDATE()"); // 数据库层面也设置默认值,防止手动插入为空

    modelBuilder.Entity<Person>()
        .Property(p => p.UpdateDateTimeUtc)
        .ValueGeneratedOnAddOrUpdate()
        .HasDefaultValueSql("GETUTCDATE()"); // 插入时自动填充初始UTC时间
}
方案二:数据库触发器实现(全局生效)

这种方式是在数据库层面创建触发器,不管是通过EF修改还是直接用SQL语句修改数据,都会自动更新时间戳,适合需要全局生效的场景。

步骤1:配置实体属性

同样在OnModelCreating里配置两个时间字段:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .Property(p => p.InsertDateTimeUtc)
        .ValueGeneratedOnAdd()
        .HasDefaultValueSql("GETUTCDATE()");

    modelBuilder.Entity<Person>()
        .Property(p => p.UpdateDateTimeUtc)
        .ValueGeneratedOnAddOrUpdate()
        .HasDefaultValueSql("GETUTCDATE()"); // 插入时初始化时间
}

步骤2:添加数据库触发器

生成EF迁移后,打开迁移文件,在Up方法里添加创建触发器的SQL,Down方法里添加删除触发器的SQL:

public partial class AddPersonUpdateTrigger : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        // 保留迁移自动生成的代码

        // 添加触发器
        migrationBuilder.Sql(@"
            CREATE TRIGGER [dbo].[Trigger_Person_UpdateTimestamp]
            ON [dbo].[Persons]
            AFTER UPDATE
            AS
            BEGIN
                SET NOCOUNT ON;
                UPDATE p
                SET p.UpdateDateTimeUtc = GETUTCDATE()
                FROM [dbo].[Persons] p
                INNER JOIN inserted i ON p.Id = i.Id;
            END");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        // 保留迁移自动生成的代码

        // 删除触发器
        migrationBuilder.Sql(@"DROP TRIGGER [dbo].[Trigger_Person_UpdateTimestamp]");
    }
}

方案对比

  • 方案一:轻量、无需数据库操作,适合仅通过EF操作数据的场景;但直接修改数据库时不会更新时间。
  • 方案二:全局生效,不管用什么方式修改数据都会更新时间;但需要维护触发器,多表时要创建多个触发器。

内容的提问来源于stack exchange,提问作者ChW

火山引擎 最新活动