如何让EF Core在Update/SaveChanges时获取CreatedAt字段值?
我有如下模型:
class Client { public int id {get; set;} ... public DateTime CreatedAt {get; set;} }
并进行了如下类型配置(其他字段为默认配置):
builder.Property(p => p.CreatedAt) .HasDefaultValueSql("now() at time zone 'utc'") .ValueGeneratedOnAdd() .Metadata.SetAfterSaveBehavior(Microsoft.EntityFrameworkCore.Metadata.PropertySaveBehavior.Ignore);
执行更新操作时,我不想提供CreatedAt值,希望它始终保持不变,因此API的PUT(更新)接口不接收CreatedAt参数。设置Metadata.SetAfterSaveBehavior是为了在更新时忽略CreatedAt字段,但SaveChanges()后该字段仍为null/默认值。
我希望能让EF Core在更新操作时获取CreatedAt字段的值,即使不更新它。我知道可以使用Reload()方法重新加载所有字段,但这会让一次更新操作执行两次数据库请求,而第二次请求只是为了获取CreatedAt字段值,这并不理想。
方案1:更新前仅查询CreatedAt字段(轻量双请求)
在执行更新逻辑前,单独查询目标实体的CreatedAt值并赋值给待更新实体,这样EF Core跟踪实体时会保留该值,更新后实体的CreatedAt就是数据库中的真实值:
// 假设待更新实体为updatedClient var existingCreatedAt = _context.Clients .Where(c => c.id == updatedClient.id) .Select(c => c.CreatedAt) .FirstOrDefault(); updatedClient.CreatedAt = existingCreatedAt; _context.Update(updatedClient); await _context.SaveChangesAsync();
该方案仅查询单个字段,比Reload()加载整个实体的性能开销更小。
方案2:使用数据库返回值实现单请求更新+获取(最优)
通过执行原生SQL更新语句,利用数据库的RETURNING语法(以PostgreSQL为例,SQL Server可使用OUTPUT语法),在更新完成后直接返回CreatedAt值,仅需一次数据库请求:
// 示例:更新Name字段并返回CreatedAt var createdAt = await _context.Database.QuerySingleAsync<DateTime>(@" UPDATE Clients SET Name = @p1 WHERE id = @p0 RETURNING CreatedAt;", updatedClient.id, updatedClient.Name); updatedClient.CreatedAt = createdAt;
这种方式既完成了更新操作,又获取到了最新的CreatedAt值,避免了额外的查询请求。
方案3:调整实体跟踪逻辑
如果待更新实体是通过EF Core上下文跟踪的(比如先查询再修改),可以标记CreatedAt字段为未修改,确保EF Core保留原始跟踪值:
// 先查询获取跟踪的实体 var client = await _context.Clients.FindAsync(updatedClient.id); // 修改需要更新的字段 client.Name = updatedClient.Name; // 明确标记CreatedAt无需修改(默认已跟踪原始值,此步骤可省略) _context.Entry(client).Property(c => c.CreatedAt).IsModified = false; await _context.SaveChangesAsync();
这种情况下,EF Core会保留查询时获取的CreatedAt值,更新后实体的该字段不会被重置。
内容的提问来源于stack exchange,提问作者Oleh Tymoshenko

