Adding CreatedDate to an entity using Entity Framework 5 Code First

29,653

Solution 1

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()"));

Solution 2

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.

Solution 3

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

Solution 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.

Solution 5

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; }
}
Share:
29,653
Brian Ogden
Author by

Brian Ogden

LinkedIn: https://www.linkedin.com/in/sweetog

Updated on October 23, 2020

Comments

  • Brian Ogden
    Brian Ogden over 3 years

    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.

  • Brian Ogden
    Brian Ogden over 11 years
    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
  • KtorZ
    KtorZ over 11 years
    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)
  • Brian Ogden
    Brian Ogden over 11 years
    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
    Brian Ogden over 11 years
    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.
  • Adolfo Perez
    Adolfo Perez almost 11 years
    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
  • Nicolas Fall
    Nicolas Fall over 10 years
    Only if you are ok with the annotations going on your classes, I used the SaveChanges method answer instead.
  • z-boss
    z-boss about 10 years
    @Rusty Divine: For some reason always sets it to 01/01/1900 12:00:00 AM
  • Maarten
    Maarten about 10 years
    @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.
  • Dragouf
    Dragouf about 10 years
    I found a better way to do it here : andy.mehalick.com/2014/02/06/…
  • Matt R
    Matt R over 9 years
    If the value is generated, do you need a set? Or can you make the property have only a get?
  • DLeh
    DLeh over 9 years
    Note that the EntityState enum is in System.Data.Entity (EF6 at least)
  • ANeves
    ANeves over 9 years
    You should store DateTime.UtcNow instead of a local date.
  • CMS
    CMS almost 9 years
    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)
  • sfs
    sfs over 8 years
    Same solution but includes modified date and the user who made the change - benjii.me/2014/03/…
  • Rudey
    Rudey over 7 years
    You'll probably want to use "GETUTCDATE()".
  • alex
    alex over 7 years
    in my migration's Up() method - What does this mean?
  • Alin Ciocan
    Alin Ciocan over 7 years
    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).
  • Brian Ogden
    Brian Ogden over 7 years
    @alex migration is code first, for database first you just use sql server management studio and set the default value for the column
  • Majid Akbari
    Majid Akbari over 3 years
    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).