You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Entity Framework中ValueGeneratedOnAddOrUpdate不生效,如何自动生成时间字段?

问题

使用Entity Framework向表中添加和更新记录时,无法让CreatedAt和ModifiedAt字段自动生成值。尝试使用ValueGeneratedOnAddOrUpdate()属性,但未达到预期效果,相关代码片段如下:

using FazendaEletronica.Domain.Entity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace FazendaEletronica.Infra.Data.EntitiesConfiguration
{
    public class AnimalConfiguration : IEntityTypeConfiguration<Animal>
    {
        public void Configure(EntityTypeBuilder<Animal> builder)
        {
            builder.HasKey(t => t.Id);
            builder.Property(p => p.Number).IsRequired();
            builder.Property(p => p.SurName).HasMaxLength(100);
            builder.Property(p => p.BirthDay).IsRequired().HasColumnType("Date");
            builder.Property(p => p.CharAnimalSex).HasMaxLength(1).IsRequired();
            builder.Property(p => p.AnimalStatus).IsRequired();
            builder.Property(p => p.Active).IsRequired().HasColumnType("bit");
            builder.Property(p => p.SaleDate).HasColumnType("Date");
            builder.HasOne(a => a.AnimalDad).WithMany(a => a.AnimalsDad).HasForeignKey(a => a.AnimalDadId).IsRequired(false);
            builder.HasOne(p => p.AnimalClass).WithMany(a => a.Animals).HasForeignKey(f => f.AnimalClassId).IsRequired();
            builder.HasOne(p => p.AnimalPurpose).WithMany(a => a.Animals).HasForeignKey(f => f.AnimalPurposeId).IsRequired();
            builder.HasOne(p => p.UserFarm).WithMany(p => p.Animals).HasForeignKey(f => f.UserFarmId).IsRequired();

            builder.Property(p => p.ModifiedAt).ValueGeneratedOnAddOrUpdate();
            builder.Property(p => p.CreatedAt).ValueGeneratedOnAdd();
        }
    }
}

想知道为什么ValueGeneratedOnAddOrUpdate()无法自动生成值,以及其他实现该效果的方法。


原因分析

ValueGeneratedOnAddOrUpdate()仅用于告知EF Core:该属性的值由数据库自动生成,EF Core在插入/更新时不应覆盖此值,或在操作后重新获取生成的值。但它不会主动生成或设置值——如果数据库本身没有为该字段配置自动生成规则(比如SQL Server的DEFAULT GETDATE()),EF Core不会自动填充这些字段的内容。

另外,DateTime类型字段并非EF Core默认支持自动生成的类型,必须配合数据库配置或代码逻辑才能实现自动赋值。


解决方案

以下是几种可靠的实现方式:

1. 数据库层面配置默认值

在实体配置中同时设置数据库默认值,结合ValueGeneratedOnAddOrUpdate()实现自动生成:

// CreatedAt:插入时使用数据库当前时间
builder.Property(p => p.CreatedAt)
       .HasDefaultValueSql("GETDATE()")
       .ValueGeneratedOnAdd();

// ModifiedAt:插入和更新时都使用数据库当前时间
builder.Property(p => p.ModifiedAt)
       .HasDefaultValueSql("GETDATE()")
       .ValueGeneratedOnAddOrUpdate();

注意:不同数据库的时间函数不同,比如MySQL用NOW(),PostgreSQL用CURRENT_TIMESTAMP。

2. 重写DbContext的SaveChanges方法

在DbContext中统一处理所有实体的时间戳字段,无需每个实体单独配置:

public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
    UpdateTimestamps();
    return base.SaveChanges(acceptAllChangesOnSuccess);
}

public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
    UpdateTimestamps();
    return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}

private void UpdateTimestamps()
{
    var entries = ChangeTracker.Entries()
        .Where(e => e.Entity is BaseEntity && (e.State == EntityState.Added || e.State == EntityState.Modified));

    foreach (var entry in entries)
    {
        var now = DateTime.UtcNow; // 建议使用UTC时间避免时区问题
        if (entry.State == EntityState.Added)
        {
            ((BaseEntity)entry.Entity).CreatedAt = now;
        }
        ((BaseEntity)entry.Entity).ModifiedAt = now;
    }
}

需要先定义包含时间戳字段的基类,让所有需要该功能的实体继承:

public abstract class BaseEntity
{
    public DateTime CreatedAt { get; set; }
    public DateTime ModifiedAt { get; set; }
    // 可添加其他公共字段如Id等
}

3. 使用EF Core拦截器

EF Core 3.0+支持拦截器,可在执行SQL前自动处理时间戳逻辑,无需修改实体基类:

public class TimestampInterceptor : SaveChangesInterceptor
{
    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
    {
        var context = eventData.Context;
        if (context == null) return base.SavingChangesAsync(eventData, result, cancellationToken);

        var entries = context.ChangeTracker.Entries()
            .Where(e => e.State is EntityState.Added or EntityState.Modified);

        foreach (var entry in entries)
        {
            var now = DateTime.UtcNow;
            if (entry.State == EntityState.Added && entry.Property("CreatedAt") != null)
            {
                entry.Property("CreatedAt").CurrentValue = now;
            }
            if (entry.Property("ModifiedAt") != null)
            {
                entry.Property("ModifiedAt").CurrentValue = now;
            }
        }

        return base.SavingChangesAsync(eventData, result, cancellationToken);
    }
}

然后在DbContext的OnConfiguring方法中注册拦截器:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.AddInterceptors(new TimestampInterceptor());
}

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 11:03:13