Looking forward to build a framework, (No repository pattern to working with DbSets directly) to autopopulate Created and last modified automatically, rather than spitting out these codes through out code base.
Could you point me out in right direction how to achieve it.
In past I tried populating these in constructors, however that seems
like a nasty code and every time we pull up somting from database EF
change tracking will mark the entity as modified.
.ctor()
{
Created = DateTime.Now;
LastModified = DateTime.Now;
}
public interface IHasCreationLastModified
{
DateTime Created { get; set; }
DateTime? LastModified { get; set; }
}
public class Account : IEntity, IHasCreationLastModified
{
public long Id { get; set; }
public DateTime Created { get; set; }
public DateTime? LastModified { get; set; }
public virtual IdentityUser IdentityUser { get; set; }
}
Starting with v2.1, EF Core provides State change events:
New Tracked
And StateChanged
events on ChangeTracker
can be used to write logic that reacts to entities entering the DbContext
or changing their state.
You can subscribe to these events from inside your DbContext
constructor
ChangeTracker.Tracked += OnEntityTracked;
ChangeTracker.StateChanged += OnEntityStateChanged;
and do something like this:
void OnEntityTracked(object sender, EntityTrackedEventArgs e)
{
if (!e.FromQuery && e.Entry.State == EntityState.Added && e.Entry.Entity is IHasCreationLastModified entity)
entity.Created = DateTime.Now;
}
void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{
if (e.NewState == EntityState.Modified && e.Entry.Entity is IHasCreationLastModified entity)
entity.LastModified = DateTime.Now;
}
One possible solution (the one we're currently using where I work) is to override the SaveChanges()
method in the DbContext and add a little bit of code to loop through changed entities setting the values
public override int SaveChanges()
{
var changedEntriesCopy = ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added ||
e.State == EntityState.Modified ||
e.State == EntityState.Deleted)
.ToList();
var saveTime = DateTime.Now;
foreach (var entityEntry in changedEntriesCopy)
{
if (entityEntry.Metadata.FindProperty("Created") != null && entityEntry.Property("Created").CurrentValue == null)
{
entityEntry.Property("Created").CurrentValue = saveTime;
}
if (entityEntry.Metadata.FindProperty("Updated") != null)
{
entityEntry.Property("Updated").CurrentValue = saveTime;
}
}
return base.SaveChanges();
}
Our situation is basically set up similar to that - Created in our situation is nullable so that when a new entity is created the value is null in code up to creation (makes it easy to check if Created has been populated ever).
There may be other solutions, but this was the easiest to get set up for us, and hasnt had any noticeable performance impact