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