I have exactly the same layout as you with what I call "Audit" fields.
The way I solved this was to create a base abstract class called AuditableEntity to hold the properties themselves and expose a method called PrepareSave. Inside PrepareSave I set the values of the fields as required:
public abstract class AuditableEntity
{
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
public DateTime UpdatedDate { get; set; }
public string UpdatedBy { get; set; }
public virtual void PrepareSave(EntityState state)
{
var identityName = Thread.CurrentPrincipal.Identity.Name;
var now = DateTime.UtcNow;
if (state == EntityState.Added)
{
CreatedBy = identityName ?? "unknown";
CreatedDate = now;
}
UpdatedBy = identityName ?? "unknown";
UpdatedDate = now;
}
}
I made PrepareSave virtual so I can override it in my entities if I want. You may need to change how you get the Identity depending on your implementation.
To call this, I overwrote SaveChanges on my DbContext and called PrepareSave on each entity that was being added or updated (which I got from the change tracker):
public override int SaveChanges()
{
// get entries that are being Added or Updated
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added || x.State == EntityState.Modified);
foreach (var entry in modifiedEntries)
{
// try and convert to an Auditable Entity
var entity = entry.Entity as AuditableEntity;
// call PrepareSave on the entity, telling it the state it is in
entity?.PrepareSave(entry.State);
}
var result = base.SaveChanges();
return result;
}
Now, whenever I call SaveChanges on my DbContext (either directly or through a repository), any entity that inherits AuditableEntity will have it's audit fields set as necessary.