Adding CreatedDate to an entity using Entity Framework 5 Code First
I

7

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.

Italic answered 17/1, 2013 at 18:14 Comment(1)
For an EF-core solution, see this question.Furey
G
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()"));
Greg answered 18/1, 2013 at 12:24 Comment(9)
Only if you are ok with the annotations going on your classes, I used the SaveChanges method answer instead.Yonit
@Rusty Divine: For some reason always sets it to 01/01/1900 12:00:00 AMConeflower
I found a better way to do it here : andy.mehalick.com/2014/02/06/…Acadian
If the value is generated, do you need a set? Or can you make the property have only a get?Levana
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)Calvillo
You'll probably want to use "GETUTCDATE()".Meddle
in my migration's Up() method - What does this mean?Melancholia
@Melancholia migration is code first, for database first you just use sql server management studio and set the default value for the columnItalic
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).Weevil
H
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.

Homophonous answered 17/1, 2013 at 18:30 Comment(7)
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 errorItalic
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)Homophonous
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.Italic
Note that the EntityState enum is in System.Data.Entity (EF6 at least)Ripuarian
You should store DateTime.UtcNow instead of a local date.Karissakarita
Same solution but includes modified date and the user who made the change - benjii.me/2014/03/…Bengal
First of all the advice from @Karissakarita 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).Danyel
R
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();
        }
Rede answered 28/6, 2015 at 9:39 Comment(0)
I
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.

Italic answered 18/1, 2013 at 4:47 Comment(0)
B
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; }
}
Bimah answered 18/1, 2013 at 10:47 Comment(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 crazyOvoid
@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.Olivine
M
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()");
}
Megaphone answered 23/10, 2018 at 7:55 Comment(0)
H
-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.

Howes answered 17/1, 2013 at 18:19 Comment(1)
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.Italic

© 2022 - 2024 — McMap. All rights reserved.