5

I want to have basically on most of my tables a "Last Updated" column so that I can quickly check when it was last updated.

Right now I been just putting a datetime and every time I do an action in my C# code I will save the new DateTime to update that field. Of course this can lead to me forgetting to do this.

I want something more automatic. I don't need this really for auditing purposes so it does not need to be too advanced.

I tried

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

what should make a timestamp but when I look at in the database I see 0x00000000000007D1 when I thought it would look more like a datetime.

edit

 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");
        }
    }
chobo2
  • 83,322
  • 195
  • 530
  • 832
  • [What is difference between datetime and timestamp](https://stackoverflow.com/questions/4460197/what-is-difference-between-datetime-and-timestamp) should be helpful. TLRD: RowVersion is simply not what-so-ever a value that can be converted to time. – Erik Philips Mar 15 '19 at 20:27

3 Answers3

6

Assuming you have defined a shadow property LastModified for each entity you want that functionality as in your example

builder.Property<DateTime>("LastUpdated");

and the question is how to update it automatically.

The technique described in the David's answer is outdated. It's still possible to override SaveChanges (and SaveChangesAsync), but there is IMHO a better approach, shown in Populate Created and LastModified automagically in EF Core. It's interface based approach, but can easily be adjusted for shadow property (or any property - the methods used work for both shadow and explicit properties).

Quick recap: Starting with v2.1, EF Core provides State change events:

New Tracked And StateChanged events on ChangeTracker can be used to write logic that reacts to entities entering the DbContext or changing their state.

Here is how you can utilize them. Add the following to your derived context class:

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;
    }
}

and add

SubscribeStateChangeEvents();

to your derived context constructor(s).

And that's all. Once the context is subscribed to these events, it will be notified anytime the entity is initially tracked or its state changes. You are interested only with Modified and Added state not triggered by query materialization (if you don't need Added, then just exclude it from the if condition). Then you check if the entity contains DateTime LastModified property using the EF Core metadata services and if yes, simply update it with the current date/time.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • Cool this seems to be working, the one thing with these shadow property, what happens if I want to check the value or query based on a certain date? How do I do that in linq? Do I add a LastUpdated property? – chobo2 Mar 21 '19 at 21:24
  • 2
    You could use [EF.Property](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.ef.property?view=efcore-2.1) method to access such fields inside LINQ to Entities query, which means you can do filter, sort, `select` etc. e.g. `db.Set().Where(e => EF.Property(e, "LastModified") < someDate).OrderByDescending(e => EF.Property(e, "LastModified"))` etc. But if you need that often, adding explicit property would be better. The good thing is, explicit or shadow, the change tracking handling code will be the same. – Ivan Stoev Mar 22 '19 at 01:01
  • 1
    So I would have in my config builder.Property("LastUpdated"); and in my model I would have public DateTime LastUpated {get; set;} – chobo2 Mar 22 '19 at 17:37
  • Well, if you have `public DateTime LastUpdated {get; set;}` in the model, you don't need `builder.Property("LastUpdated");` (althought it won't hurt). What I was trying to say was that the code in the answer will work regardless of whether the entity has explicit property or shadow property - as soon as it's called "LastUpdated" and is of type `DateTime`. Think of it like universal code for accessing property, similar to reflection, but using EF Core metadata model. – Ivan Stoev Mar 22 '19 at 17:58
  • Hey sorry to bring this up again but it is not working on one of my modals. I am doing this var found = dbContext.Tracking.FirstOrDefault(x => x.Token == token); found.SomeField = "1541515"; dbContext.SaveChanges(); but the LastUpdated is not automatically updated. It seems to skip it because of "OnEntityTracked" FromQuery is True – chobo2 May 06 '19 at 22:43
  • I guess this could be reguarding your comment about "query materialization" though I thought since I am updating the entity it would have be considered "modified" – chobo2 May 06 '19 at 22:45
  • As part of the `FirstOrDefault` call, yes, it is coming from query and should be skipped (the state should be `Unchanged` anyway). But after setting some field and calling `SaveChanges`, it should go through `OnEntityStateChanged` because the state should change to `Modified`. Check if `dbContext.ChangeTracker.AutoDetectChangesEnabled` is `true`. If not, you should call `dbContext.ChangeTracker.DetectChanges();` manually. – Ivan Stoev May 07 '19 at 00:01
1

The pattern in EF Core for this is to make the "LastUpdated" a Shadow Property, and update it during SaveChanges().

See, eg: Implementing Common Audit Fields with EF Core’s Shadow Property

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • So I add " builder.Property("LastUpdated");" to all my config files? Then where do I override saveChanges? What happens if I do not want all tables to have this lastUpdated on it? – chobo2 Mar 15 '19 at 20:01
  • SaveChanges is a DbContext method. You override it in your DbContext subtype. If you don't want all your tables to have the property, you just need to keep track of which ones do and don't, or examine them at runtime as in the linked blog. – David Browne - Microsoft Mar 15 '19 at 20:28
  • hmm, my db conext file has class ApplicationDbContext : IdentityDbContext which does not have SaveChanges. Do I need to also add : DbContext to it? – chobo2 Mar 15 '19 at 22:14
0

By looking at the DbContext class documentation, you can override SaveChanges method and set the LastUpdated values in there, before continuing with base.SaveChanges() at the end.

One last piece - inside SaveChanges method, you can find the modified/newly added entities using ChangeTracker property.

Tengiz
  • 8,011
  • 30
  • 39