EF Core 2.1和Sql Server的TimeStamp是什么?

5

我希望在我的大多数表格上都有一个“最后更新”列,以便我可以快速检查上次更新的时间。

现在,我只是放置了一个日期时间,并且每次在我的C#代码中执行操作时,我会保存新的日期时间以更新该字段。当然,这可能导致我忘记执行此操作。

我希望有一些更自动化的方式。我不需要这个功能用于审计目的,因此它不需要太高级。

我试过

builder.Property(x => x.RowVersion).IsRowVersion();

如何生成时间戳,但我在数据库中查看时看到的是0x00000000000007D1,而不是像日期时间一样的格式。

编辑

 public class CompanyConfig : IEntityTypeConfiguration<Company>
    {
        public void Configure(EntityTypeBuilder<Company> builder)
        {
            builder.HasKey(x => x.Id);
            builder.Property(x => x.Id).ValueGeneratedOnAdd();
            builder.Property<DateTime>("LastUpdated");
        }
    }

“datetime”和“timestamp”的区别是什么?这篇文章可能会有所帮助。简而言之,RowVersion根本不是可以转换为时间的值。 - Erik Philips
3个回答

6
假设您已经为每个实体定义了一个影子属性LastModified,就像您的示例中那样。
builder.Property<DateTime>("LastUpdated");

问题是如何自动更新它。

David的回答中描述的技术已经过时了。仍然可以重写 SaveChanges (和 SaveChangesAsync),但我认为有一种更好的方法,显示在 EF Core中自动填充Created和LastModified 。这是基于接口的方法,但很容易调整为影子属性(或任何属性 - 使用的方法适用于影子和显式属性)。

快速回顾:从v2.1开始,EF Core提供了 状态更改事件

ChangeTracker上的新 TrackedStateChanged事件可用于编写对实体进入DbContext或更改其状态做出反应的逻辑。

以下是如何利用它们的方法。将以下内容添加到派生的上下文类中:

void SubscribeStateChangeEvents()
{
    ChangeTracker.Tracked += OnEntityTracked;
    ChangeTracker.StateChanged += OnEntityStateChanged;
}

void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{
    ProcessLastModified(e.Entry);
}

void OnEntityTracked(object sender, EntityTrackedEventArgs e)
{
    if (!e.FromQuery)
        ProcessLastModified(e.Entry);
}

void ProcessLastModified(EntityEntry entry)
{
    if (entry.State == EntityState.Modified || entry.State == EntityState.Added)
    {
        var property = entry.Metadata.FindProperty("LastModified");
        if (property != null && property.ClrType == typeof(DateTime))
            entry.CurrentValues[property] = DateTime.UtcNow;
    }
}

并添加

SubscribeStateChangeEvents();

让你的派生上下文构造函数来订阅这些事件。

这就是全部内容了。一旦上下文订阅了这些事件,每当实体被初始化跟踪或其状态更改时,都会收到通知。您只对触发的ModifiedAdded状态感兴趣(如果您不需要Added,则从if条件中排除它即可)。然后,您可以使用 EF Core 元数据服务检查实体是否包含 DateTime LastModified 属性,如果是,则将其更新为当前日期/时间。


2
你可以使用 EF.Property 方法来访问 LINQ to Entities 查询中的这些字段,这意味着你可以进行筛选、排序、选择等操作。例如: db.Set<Company>().Where(e => EF.Property<DateTime>(e, "LastModified") < someDate).OrderByDescending(e => EF.Property<DateTime>(e, "LastModified")) 等。但如果你需要经常这样做,添加显式属性会更好。好处是,无论是显式属性还是阴影属性,变更跟踪处理代码都将是相同的。 - Ivan Stoev
1
所以在我的配置中,我会有builder.Property<DateTime>("LastUpdated"); 而在我的模型中,我会有public DateTime LastUpated {get; set;}。 - chobo2
如果您的模型中有 public DateTime LastUpdated {get; set;},则不需要 builder.Property<DateTime>("LastUpdated");(尽管这样做也不会有害)。我想说的是,答案中的代码将适用于实体是否具有显式属性或阴影属性 - 只要它被称为“LastUpdated”并且是 DateTime 类型。将其视为访问属性的通用代码,类似于反射,但使用 EF Core 元数据模型。 - Ivan Stoev
抱歉再次提起这个问题,但它在我的一个模态上无法正常工作。我正在执行以下操作:var found = dbContext.Tracking.FirstOrDefault(x => x.Token == token); found.SomeField = "1541515"; dbContext.SaveChanges(); 但是LastUpdated没有自动更新。似乎由于"OnEntityTracked" FromQuery为True而跳过了它。 - chobo2
作为 FirstOrDefault 调用的一部分,是的,它来自查询并且应该被跳过(状态应该始终为 Unchanged)。但在设置某些字段并调用 SaveChanges 后,它应该通过 OnEntityStateChanged,因为状态应该更改为 Modified。检查 dbContext.ChangeTracker.AutoDetectChangesEnabled 是否为 true。如果不是,则应手动调用 dbContext.ChangeTracker.DetectChanges(); - Ivan Stoev
显示剩余2条评论

1

那我是要在所有的配置文件中添加 "builder.Property<DateTime>("LastUpdated");" 吗?那我该在哪里重写 saveChanges 呢?如果我不想让所有表都有这个 lastUpdated 字段怎么办? - chobo2
SaveChanges是一个DbContext方法。您可以在DbContext子类型中覆盖它。如果您不希望所有表都具有该属性,只需跟踪哪些表具有该属性,或者像链接的博客中所述,在运行时检查它们即可。 - David Browne - Microsoft
嗯,我的数据库上下文文件有一个类 ApplicationDbContext : IdentityDbContext<Employee>,它没有 SaveChanges 方法。我需要同时添加 : DbContext 吗? - chobo2

0
通过查看DbContext类文档,您可以覆盖SaveChanges方法并在其中设置LastUpdated值,然后继续使用base.SaveChanges()来完成。
最后一个小细节 - 在SaveChanges方法内部,您可以使用ChangeTracker属性找到已修改/新添加的实体。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接