如何正确使用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




