Adding CreatedDate to an entity using Entity Frame

2019-01-21 13:36发布

问题:

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.

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


回答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.



回答3:

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.



回答4:

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


回答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; }
}


回答6:

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


回答7:

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.