8

I have CreatedAt and UpdatedAt columns in my User model.

User.cs

public string Name { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }

Requirement

  • When we SaveChanges() user records, CreatedAt and UpdatedAt should automatically saved e.g: DateTime.UtcNow
  • When I update User record, only UpdatedAt column should get updated to current date time.
  • And this should happen automatically for all other models, may be some configuration in OnModelCreating().
  • I want this behavior to find latest records from the database, and other places too.
  • I am using code first migration approach
  • I am using MySQL server, MySql.Data, MySql.Data.Entity.EF6.

UPDATE

I added BaseEntity.cs model

public abstract class BaseEntity
    {
        public DateTime CreatedAt { get; set; }
        public DateTime UpdatedAt { get; set; }
    }

Inheriting User from BaseEntity

public class User : BaseEntity
{
  public int Id { get; set; }
  public int FullName { get; set; }
}

and updated migrations to include defaultValueSql()

AddColumn("dbo.Users", "CreatedAt", c => c.DateTime(nullable: false, precision: 0, defaultValueSql: "NOW()"));
AddColumn("dbo.Users", "UpdatedAt", c => c.DateTime(nullable: false, precision: 0, defaultValueSql: "NOW()"));"

Now, need a way to fix UpdatedAt column in each update.

przbadu
  • 5,769
  • 5
  • 42
  • 67
  • use database first, then the implement by sql? – Lei Yang Aug 01 '17 at 06:08
  • No, I am using code first. I found `AddColumn("dbo.Users", "CreatedAt", c => c.DateTime(nullable: false, precision: 0, defaultValueSql: "UTC_TIMESTAMP()"));` but `update-migration` is giving syntax error. – przbadu Aug 01 '17 at 06:17
  • but running `select UTC_TIMESTAMP();` in mysql query gives current date time stamp – przbadu Aug 01 '17 at 06:18
  • `You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'UTC_TIMESTAMP()' at line 1` – przbadu Aug 01 '17 at 06:19

3 Answers3

20

Finally, Found solution for my problem. Because we can change our database from MySql to postgresql or Ms Sql server, so adding default value using sql query doesn't seems like correct solution.

Here is how I have solved it.

Add Base model

 public abstract class BaseEntity
 {
    public DateTime? CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
 }

Inherit all your models from this base model, In my case it is User

public class User : BaseEntity
{
  public int Id { get; set; }
  public int FullName { get; set; }
}

don't forget to generate migrations if you are using code first approach. Migration should be simple enough:

Example:

AddColumn("dbo.Users", "CreatedAt", c => c.DateTime(precision: 0));
AddColumn("dbo.Users", "UpdatedAt", c => c.DateTime(precision: 0));

And Final step is to override SaveChanges() and SaveChangesAsync() in your context:

public class MyDbContext : DbContext
{
    public DbSet<User> Users { get; set; }

    public override int SaveChanges()
    {
        AddTimestamps();
        return base.SaveChanges();
    }

    public override async Task<int> SaveChangesAsync()
    {
        AddTimestamps();
        return await base.SaveChangesAsync();
    }

    private void AddTimestamps()
    {
        var entities = ChangeTracker.Entries()
            .Where(x => x.Entity is BaseEntity && (x.State == EntityState.Added || x.State == EntityState.Modified));

        foreach (var entity in entities)
        {
            var now = DateTime.UtcNow; // current datetime

            if (entity.State == EntityState.Added)
            {
                ((BaseEntity)entity.Entity).CreatedAt = now;
            }
            ((BaseEntity)entity.Entity).UpdatedAt = now;
        }
    }
}
przbadu
  • 5,769
  • 5
  • 42
  • 67
  • 1
    Note that for those using EF Core, this does not work. Entities cannot yet inherit fields from abstract base classes. MS calls this Table-Per-Concrete-Type, and it's not yet supported. Feature release target is EF 5.0, which is currently in preview so hopefully soon. This would make many domain models much cleaner. https://learn.microsoft.com/en-us/ef/core/modeling/inheritance – Ethan Hohensee Apr 02 '20 at 18:25
1

You could define yourself a interface like this:

public interface ITiming {
  DateTime CreatedAt {get; set; }
  DateTime UpdatedAt {get; set; }
}

Now let every model you want implement this interface. And write yourselve a ExtionsionMethod like this:

public static SaveChangesTimed(this YourDbContext _context, ITiming timed) {
   timed.CreatedAt = DateTime.Now;
   timed.UpdatedAt = Datetime.Now;
   _context.SaveChanges();
}
Yggraz
  • 114
  • 7
  • Thanks Yggraz, but I have one question though: It looks like it will update `CreatedAt` and `UpdatedAt` everytime `SaveChanges()` is hit, but I want to updated `CreatedAt` for the first time and it should not change from that point, only `UpdatedAt` should change from next time. Please correct me if I misunderstood. – przbadu Aug 01 '17 at 07:16
  • yes, if you want this, then you have to autoimplement `CreatedAt` [link](https://stackoverflow.com/questions/8594431/possible-to-default-datetime-field-to-getdate-with-entity-framework-migrations) and use the Extension just for UpdatedAt – Yggraz Aug 02 '17 at 08:22
1

I saw the @przbadu's post and I had a little difficult. I am using dotnet core 2.2 and my override I needed to change the SaveChangeAsync method to:

public override int SaveChanges()
{
    AddTimestamps();
    return base.SaveChanges();
}

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
    AddTimestamps();
    return base.SaveChangesAsync();
}

private void AddTimestamps()
{
    var entities = ChangeTracker.Entries()
        .Where(x => x.Entity is BaseEntity && (x.State == EntityState.Added || x.State == EntityState.Modified));

    foreach (var entity in entities)
    {
        var now = DateTime.UtcNow; // current datetime

        if (entity.State == EntityState.Added)
        {
            ((BaseEntity)entity.Entity).CreatedAt = now;
        }
        ((BaseEntity)entity.Entity).UpdatedAt = now;
    }
}
lucas teles
  • 680
  • 9
  • 8