8

I have the following entity:

public class User
{
    public int UserId { get; set; }
    public string UserName { get; set; }
    public string UserAddress { get; set; }
    public DateTime CreateDate { get; set; }
    public DateTime? UpdateDate { get; set; }
}

How can I make the UpdateDate field of the User entity become the server DateTime on an update? That is to say, whenever an entity in the database becomes modified, the UpdateDate field changes to that exact date and time as well as if to say UpdateDate = new DateTime(Datetime.Now.Ticks)

alvonellos
  • 1,009
  • 1
  • 9
  • 27
cristianqr
  • 668
  • 2
  • 9
  • 21
  • @Patrick: Hi, I want to assign the server date to UpdateDate field when use context.SaveChanges(), after update the record. – cristianqr Jul 05 '14 at 23:04
  • See: http://stackoverflow.com/questions/3879011/entity-framework-sql2008-how-to-automatically-update-lastmodified-fields-for-e – alvonellos Jul 06 '14 at 00:37
  • 2
    The *only* way to do this correctly is by a database trigger. There's no way to tell how much latency there will be between fetching the db date/time and the actual commit of an entity. – Gert Arnold Jul 06 '14 at 08:29

4 Answers4

18

First, consider that DateTimeOffset is a better choice if you are going to be storing local times. Alternatively, you could use a UTC-based DateTime, but I will show DateTimeOffset in this example. Never store a local-based DateTime for this purpose, as you will have issues with time zones and daylight saving time. See also: The Case Against DateTime.Now.

Next, consider a common interface that you can use for entities that you wish to track created/updated values.

public interface IDatedEntity
{
    DateTimeOffset Created { get; set; }
    DateTimeOffset Updated { get; set; }
}

Apply that interface and its properties to the entities you are working with.

public class User : IDatedEntity
{
    // ...

    public DateTimeOffset Created { get; set; }
    public DateTimeOffset Updated { get; set; }
}

Now in your database context, you can attach to the SavingChanges event and apply the desired behavior:

public class MyContext : DbContext
{
    public DbSet<User> Users { get; set; }

    public MyContext()
    {
        var objectContext = ((IObjectContextAdapter)this).ObjectContext;
        objectContext.SavingChanges += (sender, args) =>
        {
            var now = DateTimeOffset.Now;
            foreach (var entry in this.ChangeTracker.Entries<IDatedEntity>())
            {
                var entity = entry.Entity;
                switch (entry.State)
                {
                    case EntityState.Added:
                        entity.Created = now;
                        entity.Updated = now;
                        break;
                    case EntityState.Modified:
                        entity.Updated = now;
                        break;
                }
            }
            this.ChangeTracker.DetectChanges();
        };
    }
}

Note the final call to DetectChanges, which will ensure that the new updated date values are applied to all modified entities.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
2

If you're using .NET Core and Entity Framework Core the easiest way I found to this is to use the built-in ChangeTracker on the database context and subscribe to its event ChangeTracker_StateChanged which will trigger any time an Entity changes state. I implemented it like this:

public abstract class BaseDbContext<T> : DbContext where T : DbContext
    {
        public BaseDbContext(DbContextOptions<T> options) : base(options)
        {
            this.ChangeTracker.StateChanged += ChangeTracker_StateChanged;
        }

        private void ChangeTracker_StateChanged(object sender, EntityStateChangedEventArgs e)
        {
            if (e.NewState == EntityState.Added || e.NewState == EntityState.Modified)
            {
                var entity = (BaseEntity)e.Entry.Entity;
                entity.UpdatedDate = DateTime.UtcNow;
            }
        }
    }

I then use this BaseDbContext across several projects all of whose entities extend a BaseEntity with an UpdatedDate field.

James Mundy
  • 4,180
  • 6
  • 35
  • 58
0

You can do this only with migrations. Set defaultSqlValue parameter for UpdateDate property to "GETDATE()" or "GETUTCDATE()" and when you modify or add new entity you would need to set its value to null.

mr100
  • 4,340
  • 2
  • 26
  • 38
  • this results into somehow an extra select query post updation, in lots of modification scenarios, this affects the performance, as it updates and selects the updated values. Can it be done without using SQL methods ? – kuldeep May 24 '18 at 10:03
0

In 2022, we can use annotations to set the computed and identity columns:

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime created { get; set; }

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

https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations#default-values

To set the default value:

// Generate default value for created and updated column types, if they exist
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
    modelBuilder.Entity(entityType.Name, x =>
    {
        if (entityType.FindProperty("created") != null)
            x.Property("created").HasDefaultValueSql("getdate()");

        if (entityType.FindProperty("updated") != null)
            x.Property("updated").HasDefaultValueSql("getdate()");
    });
}
Chris Fremgen
  • 4,649
  • 1
  • 26
  • 26
  • Just as in your [other answer](https://stackoverflow.com/a/71252866/861716) these attributes alone don't do anything, let alone set a server DateTime value. BTW, it's nothing to do with 2022. EF uses these attributes from the first day they went code first. – Gert Arnold Jun 30 '22 at 21:13
  • Setting database defaults is a good first step (provided that they use migrations),. Note that it doesn't update `UpdateDate` on each modification yet. That requires a trigger. – Gert Arnold Jul 01 '22 at 15:27