33

I am trying to add a CreatedDate property to entities in my Model and am using EF5 Code First. I want this date to not be changed once set, I want it to be a UTC date. I do NOT want to use a constructor, as I have many entities in my model that I want to inherit from an abstract class containing the CreatedDate property, and I can't enforce a constructor with an interface.

I have tried different data annotations and I have attempted to write a database initializer that would pick up a specific entity type and write an alter constraint with a getdate() default value for the correct table_name and column_name, but I have not been able to write that code correctly.

Please do not refer me to the AuditDbContext - Entity Framework Auditing Context or the EntityFramework.Extended tools, as they do not do what I need here.

UPDATE

My CreatedDate is null on SaveChanges() because I am passing a ViewModel to my view, which correctly has no audit property called CreatedDate in it. And even if I passed the model to my view, I am not editing or storing the CreatedDate in the view.

I read here that I could add the [DatabaseGenerated(DatabaseGeneratedOption.Identity)] and this would tell EF to store the CreatedDate correctly after Insert and Update, but not allow it to be changed by my application: but I just get a Cannot insert explicit value for identity column in table when IDENTITY_INSERT is set to OFF error by adding this attribute.

I am about to switch to EF Model First because this simple database requirement is ridiculous to implement in Code First.

Community
  • 1
  • 1
Brian Ogden
  • 18,439
  • 10
  • 97
  • 176

7 Answers7

71

Here is how I did it:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedDate{ get; set; }

in my migration's Up() method:

AddColumn("Agents", "CreatedDate", n => n.DateTime(nullable: false, defaultValueSql: "GETUTCDATE()"));
Rusty Divine
  • 3,462
  • 1
  • 21
  • 15
  • Only if you are ok with the annotations going on your classes, I used the SaveChanges method answer instead. – Maslow Oct 28 '13 at 16:47
  • @Rusty Divine: For some reason always sets it to 01/01/1900 12:00:00 AM – z-boss Feb 27 '14 at 01:59
  • 4
    I found a better way to do it here : https://andy.mehalick.com/2014/02/06/ef6-adding-a-created-datetime-column-automatically-with-code-first-migrations – Dragouf Apr 29 '14 at 03:36
  • If the value is generated, do you need a `set`? Or can you make the property have only a `get`? – Matt R Aug 20 '14 at 17:21
  • i did it in my MVC application and now i can update the date (i want the admin to be able to change the date if needed) – CMS Aug 04 '15 at 18:57
  • You'll probably want to use `"GETUTCDATE()"`. – Rudey Aug 23 '16 at 07:42
  • `in my migration's Up() method` - What does this mean? – alex Sep 09 '16 at 16:07
  • @alex migration is code first, for database first you just use sql server management studio and set the default value for the column – Brian Ogden Oct 28 '16 at 15:56
  • This line of code makes the application dependent on the database which is not a good practice. This property should be set by the application. (Just assume that application and database have different timezones). – Majid Akbari Oct 01 '20 at 09:55
28

Override the SaveChanges-Method in your context:

public override int SaveChanges()
{
  DateTime saveTime = DateTime.UtcNow;
  foreach (var entry in this.ChangeTracker.Entries().Where(e => e.State == (EntityState) System.Data.EntityState.Added))
   {
     if (entry.Property("CreatedDate").CurrentValue == null)
       entry.Property("CreatedDate").CurrentValue = saveTime;
    }
    return base.SaveChanges();

}

Updated because of comments: only freshly added Entities will have their Date set.

Stephan Keller
  • 1,623
  • 11
  • 13
  • Awesome advice, one problem with this though is that I am not passing the CreatedDate to my view. I am passing a ViewModel to my view and then using AutoMapper to map the fields from my ViewModel to my View so CreatedDate will always be null on Saving, I read that adding the [DatabaseGenerated(DatabaseGeneratedOption.Identity)] attribute to my CreatedDate will allow EF to correctly load data from the database, reload data after insert or update soentity is up to date in application and at the same time will not allow you to change the value but that just causes error – Brian Ogden Jan 17 '13 at 18:38
  • 6
    Not sure if I understand you correctly. It's evening here and I may have been drinking - sorry. But if the CreatedDate is not available to the context: how about a database trigger then. (Yeah, definitly been drinking when i propose *triggers*) – Stephan Keller Jan 17 '13 at 19:14
  • Well I normally would do a trigger but I am working with EF5 Code First. I could add a trigger via CodeFirst but it defeats a primary principle of CodeFirst which is to keep your business rules in one place. I can't believe that there isn't a way to set a entity property to "readonly" so to speak, so that, whether present in the context or not it doesn't update the value unless I explicitly do so. – Brian Ogden Jan 17 '13 at 19:47
  • 1
    Note that the `EntityState` enum is in `System.Data.Entity` (EF6 at least) – DLeh Oct 12 '14 at 18:57
  • 3
    You should store `DateTime.UtcNow` instead of a local date. – ANeves Nov 12 '14 at 18:08
  • Same solution but includes modified date and the user who made the change - http://benjii.me/2014/03/track-created-and-modified-fields-automatically-with-entity-framework-code-first/ – sfs Oct 26 '15 at 11:30
  • First of all the advice from @ANeves is a good practice. You should always keep your data in UTC format in the database. Secondly, this approach (sending the datetime from server to database has a huge problem if you have any SQL jobs that depends on those datetimes and the database is not synchronized with the server). – Alin Ciocan Oct 04 '16 at 12:14
6

Similar to Stephans's Answer but with Reflection and also ignores all user (external) updates Created/Updated times. Show Gist

  public override int SaveChanges()
        {
            foreach (var entry in ChangeTracker.Entries().Where(x => x.Entity.GetType().GetProperty("CreatedTime") != null))
            {
                if (entry.State == EntityState.Added)
                {
                    entry.Property("CreatedTime").CurrentValue = DateTime.Now;
                }
                else if (entry.State == EntityState.Modified)
                {
                    // Ignore the CreatedTime updates on Modified entities. 
                    entry.Property("CreatedTime").IsModified = false;
                }

                // Always set UpdatedTime. Assuming all entities having CreatedTime property
                // Also have UpdatedTime
                // entry.Property("UpdatedTime").CurrentValue = DateTime.Now;
                // I moved this part to another foreach loop
            }

            foreach (var entry in ChangeTracker.Entries().Where(
                e => 
                    e.Entity.GetType().GetProperty("UpdatedTime") != null && 
                    e.State == EntityState.Modified || 
                    e.State == EntityState.Added))
            {
                entry.Property("UpdatedTime").CurrentValue = DateTime.Now;
            }

            return base.SaveChanges();
        }
Community
  • 1
  • 1
guneysus
  • 6,203
  • 2
  • 45
  • 47
4

Ok so the primary issue here was that CreatedDate was being Updated every time I called SaveChanges and since I wasn't passing CreatedDate to my views it was being updated to NULL or MinDate by Entity Framework.

The solution was simple, knowing that I only need to set the CreatedDate when EntityState.Added, I just set my entity.CreatedDate.IsModified = false before doing any work in my SaveChanges override, that way I ignored changes from Updates and if it was an Add the CreatedDate would be set a few lines later.

Brian Ogden
  • 18,439
  • 10
  • 97
  • 176
2

Code First doesn't currently provide a mechanism for providing column default values.

You will need to manually modify or create base class to automatic update CreatedDate

public abstract class MyBaseClass
{
    public MyBaseClass()
    {
        CreatedDate = DateTime.Now;
    }
    public Datetime CreatedDate { get; set; }
}
Aiska Hendra
  • 145
  • 1
  • 4
  • 2
    But how about when you update an Entity. Initializing the CreatedDate to now in the constructor will overwrite the original CreateDate on updates. Isn't it? This is driving me crazy – Adolfo Perez Jul 16 '13 at 12:22
  • 2
    @Adolfo First the constructor runs, then the default values of local fields are set, and only after that EF will push the db-values into the in-memory entity object. So no, for existing entities, the db value of CreatedDate will not be overwritten with `DateTime.Now`. – Maarten Mar 07 '14 at 08:28
2

For EF Core you can find the MS recommended solution here: Default Values.

Use Fluent API in your DBContext:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Created)
        .HasDefaultValueSql("getdate()");
}
Laobu
  • 469
  • 1
  • 5
  • 7
-1
Accounts account;
account.Acct_JoinDate = DateTime.Now.ToUniversalTime();
data.Accounts.Add(account);
data.SaveChanges();

Why not give the timestamp upon model creation? Similar to these accounts here.

Chazt3n
  • 1,641
  • 3
  • 17
  • 42
  • 6
    I want the audit functionality to be automated and out of my controllers. I don't want other developers to have to remember to enter the CreatedDate every time they do an Add. – Brian Ogden Jan 17 '13 at 21:52