John
John

Reputation: 430

How to Add TimeStamps in Entity Framework

Coming from working with PHP, it was rather straight forward working with Timestamps but with Entity Framework, it's sort of a headache.

Upvotes: 1

Views: 9314

Answers (2)

Jeffery
Jeffery

Reputation: 388

I'm pretty sure you are using timestamps to handle some concurrency conflict. There is actually two ways in doing so in Entity Framework (Core)

  • Using a concurrency token to mark a specific property/column in your entity class as one to check for a concurrency conflict.
  • Using timestamp, which marks a whole entity class/row as one to check for a concurrency conflict.

Setting a Concurrency Check on a Property

You can configure concurrency token using Data Annotations on your Entity class.

public class BookEntity
{
    public int BookId {get; set;}
    public string Title {get; set;}
   
    // This tells EF that this property is a concurrency token,
    // which means EF will check it hasn't changed when you update it
    [ConcurrencyCheck]
    public DateTime PublishedOn {get; set;}

    // Other properties follow...
}

Concurrency check can also be configured using the fluent API

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<BookEntity>()
        .Property(p=> p.PublishedOn)
        .IsConcurrencyToken();

    // ...other configurations follow...
}

Setting a Timestamp on a Property

You can configure timestanps using Data Annotations on your Entity class.

public class BookEntity
{
    public int BookId {get; set;}
    public string Title {get; set;}
   
    // This tells EF to mark ChangeCheck property as a timestamp,
    // This causes EF to check this when updating to see if this has changed
    [Timestamp]
    public byte[] ChangeCheck {get; set;}

    // Other properties follow...
}

Configuring Timestamp using the Fluent API

protected override void OnModelCreating(ModelBuilder builder)
{
    // Value of ChangeCheck will be changed each time the row 
    // created/updated
    builder.Entity<BookEntity>()
        .Property(p=> p.ChangeCheck)
        .IsRowVersion;

    // ...other configurations follow...
}

Both configurations create a column in a table that the database server will automatically change whenever there's an INSERT or UPDATE to that table.

Setting a Computed/User Friendly TimeStamp

This can be done only via the Fluent API

public class BookEntity
{
    public int BookId {get; set;}
    public string Title {get; set;}
   
    // Column you set up as computed
    // You give it a private setter, as its a read-only property
    public DateTime DateModified {get; private set;}

    // Other properties follow...
}

Then you configure the column via the Fluent API.

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<BookEntity>()
        .Property(p=> p.DateModified)
        .HasComputedColumnSql("getutcdate()")
        .ValueGeneratedOnAddOrUpdate();

    // ...other configurations follow...
}

.HasComputedColumnSql("getutcdate()") will tell EF to compute a value for the column; in this case to get the current utc datetime, then .ValueGeneratedOnAddOrUpdate(), will let EF know that the column is computed thus should update the column on any update made.

Upvotes: 3

John
John

Reputation: 430

Here's a solution that worked for me. First I had a Baseentity that all entities inherited from:

public abstract class DbEntity
{

    [Column("created_at")]
    public DateTime CreatedAt { get; set; } = DateTime.Now;
    
    [Column("updated_at")]
    public DateTime UpdatedAt { get; set; } = DateTime.Now;
}

Then I added an extension method for the UpdateMethod:

public static class DbSetExtension
{
    public static EntityEntry<TEntity> UpdateCustom<TEntity>(this DbSet<TEntity> dbSet, TEntity dbEntity)
        where TEntity : class
    {

        dbEntity.GetType().GetProperty("UpdatedAt")?.SetValue (dbEntity, DateTime.Now, null);
            
        return dbSet.Update(dbEntity);
    }
}

This works fine for me. I'll appreciate feedback on how others handle this scenario.

P.S: The [DatabaseGenerated(DatabaseGeneratedOption.Computed)] approach didn't work for me even after setting default SQL for the columns.

Upvotes: 2

Related Questions