EF Core首次插入时如何将阴影属性值设为另一属性值?
在EF Core中同步阴影属性值并设置只读的解决方案
一、插入时让CreatedAt阴影属性匹配UpdatedAt的值
你可以通过两种方式实现插入实体时,将CreatedAt阴影属性的值设置为UpdatedAt的值:
方法1:重写DbContext的SaveChanges方法
在你的DbContext中重写SaveChanges和SaveChangesAsync方法,监听实体添加状态,同步属性值:
public class YourDbContext : DbContext { // ... 其他DbContext配置 public override int SaveChanges(bool acceptAllChangesOnSuccess) { SyncCreatedAtOnAdd(); return base.SaveChanges(acceptAllChangesOnSuccess); } public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { SyncCreatedAtOnAdd(); return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } private void SyncCreatedAtOnAdd() { foreach (var entry in ChangeTracker.Entries<Notification>() .Where(e => e.State == EntityState.Added)) { var updatedAt = entry.Property<DateTime>(nameof(Notification.UpdatedAt)).CurrentValue; entry.Property<DateTime>("CreatedAt").CurrentValue = updatedAt; } } }
方法2:使用SaveChangesInterceptor(推荐)
创建拦截器类,统一处理实体插入时的属性同步,这种方式更灵活,便于复用:
public class NotificationShadowPropertyInterceptor : SaveChangesInterceptor { public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result) { SyncCreatedAt(eventData.Context); return base.SavingChanges(eventData, result); } public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { SyncCreatedAt(eventData.Context); return base.SavingChangesAsync(eventData, result, cancellationToken); } private void SyncCreatedAt(DbContext? context) { if (context == null) return; foreach (var entry in context.ChangeTracker.Entries<Notification>() .Where(e => e.State == EntityState.Added)) { var updatedAt = entry.Property<DateTime>(nameof(Notification.UpdatedAt)).CurrentValue; entry.Property<DateTime>("CreatedAt").CurrentValue = updatedAt; } } }
然后在DbContext的配置中注册拦截器:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.AddInterceptors(new NotificationShadowPropertyInterceptor()); // ... 其他配置 }
二、将CreatedAt设置为只读,禁止修改
要彻底阻止CreatedAt被修改,需要结合模型配置和拦截器校验:
步骤1:模型配置标记为仅插入生成
在IEntityTypeConfiguration中,将CreatedAt配置为仅在插入时生成:
public void Configure(EntityTypeBuilder<Notification> builder) { // ... 其他属性配置 builder.Property(p => p.UpdatedAt).HasDefaultValueSql("GETUTCDATE()"); // 配置CreatedAt阴影属性:仅插入时生成,默认值为UTC时间 builder.Property<DateTime>("CreatedAt") .HasDefaultValueSql("GETUTCDATE()") .ValueGeneratedOnAdd(); }
步骤2:拦截器阻止修改
在之前的拦截器中添加校验逻辑,一旦检测到CreatedAt被修改,抛出异常或重置值:
private void SyncCreatedAt(DbContext? context) { if (context == null) return; // 插入时同步值 foreach (var entry in context.ChangeTracker.Entries<Notification>() .Where(e => e.State == EntityState.Added)) { var updatedAt = entry.Property<DateTime>(nameof(Notification.UpdatedAt)).CurrentValue; entry.Property<DateTime>("CreatedAt").CurrentValue = updatedAt; } // 阻止修改CreatedAt foreach (var entry in context.ChangeTracker.Entries<Notification>() .Where(e => e.State == EntityState.Modified)) { var createdAtProp = entry.Property<DateTime>("CreatedAt"); if (createdAtProp.IsModified) { // 方案1:抛出异常,明确禁止修改 throw new InvalidOperationException("CreatedAt属性为只读,不允许修改"); // 方案2:静默重置为原始值 // createdAtProp.CurrentValue = createdAtProp.OriginalValue; // createdAtProp.IsModified = false; } } }
这样就能确保CreatedAt在插入时同步UpdatedAt的值,并且后续无法被修改。
内容的提问来源于stack exchange,提问作者Shuzheng
相关产品推荐
相关产品推荐

