Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TimeStamp with EF Core 2.1 and Sql Server?

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");
        }
    }
like image 842
chobo2 Avatar asked Mar 15 '19 19:03

chobo2


2 Answers

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.

like image 154
Ivan Stoev Avatar answered Sep 28 '22 07:09

Ivan Stoev


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

like image 45
David Browne - Microsoft Avatar answered Sep 28 '22 08:09

David Browne - Microsoft