For auditing purposes, we might want our entities to have creation and/or modification timestamps (we may also extend it with some additional data, like user ID). It can be a pain to keep in mind to always set the appropriate columns, but we can make EFCore do it for us.
First we need to create some sort of interface for it, like: ICreationAudited.cs
or IAuditedEntity.cs
.
public interface IAuditedEntity
{
DateTimeOffset CreationDateTime { get; set; }
DateTimeOffset? LastModificationDateTime { get; set; }
}
Now we need to intercept the SaveChanges
calls. For this purpose let’s create a method, that will check the EFCore internal ChangeTracker
for any new entities that are implementing our IAuditedEntity
interface. We can do that by checking their state against the enums EntityState.Added
or EntityState.Modified
and setting the appropriate columns with dates.
private void SetAuditedColumns()
{
var entitiesCreated = ChangeTracker
.Entries()
.Where(e => e.Entity is IAuditedEntity
&& e.State == EntityState.Added)
.Select(x => x.Entity as IAuditedEntity);
var entitiesModified = ChangeTracker
.Entries()
.Where(e => e.Entity is IAuditedEntity
&& e.State == EntityState.Modified)
.Select(x => x.Entity as IAuditedEntity);
foreach (var entity in entitiesCreated)
{
entity.CreationDateTime = DateTimeOffset.Now;
}
foreach (var entity in entitiesModified)
{
entity.LastModificationDateTime = DateTimeOffset.Now;
}
}
Now we need to override SaveChanges
and SaveChangesAsync
methods in our ApplicationDbContext.cs
to perform our custom logic and that’s it.
public override int SaveChanges()
{
SetAuditedColumns();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync(
CancellationToken cancellationToken = default)
{
SetAuditedColumns();
return base.SaveChangesAsync(cancellationToken);
}