Audit trail and Soft Deletes in EF Core
CreatedAt, UpdatedAt, DeletedAt, CreatedBy, …, DeletedBy
I recently had to add audit fields to DB models, and make sure EF handled them automagically.
What I wanted
I wanted a CreatedAt field that was a DateTime, and aCreatedBy field, that contained the Guid of the user who was responsible for creating the model instance.
I also wanted similarUpdatedAt , and UpdatedBy fields. Nothing fancy.
It would also have been nice to be able to soft delete instances, rather than actually deleting them from the DB.
If it’s not clear, a soft delete marks a column like
IsDeletedas a boolean, so it still exists in the DB, but is not visible to the user.
Rather than use a boolean, it made sense to also have a DeletedAt DateTime field, and a DeletedBy field mapped to the user’s id.
Here’s how I expected it to work:
The Auditable abstract class
Assuming I had a Company model like:
Id => Guid
Name => String
I wanted the Company model to inherit from an Auditable class, where the base class would have fields like:
CreatedAt => DateTime
CreatedBy => TId
UpdatedAt => DateTime?
UpdatedBy => TId
DeletedAt => DateTime?
DeletedBy => TId
Did you notice that the *By fields have type TId?
That’s because it’s supposed to be a generic type, to match the
Idfield of theApplicationUsermodel from ASP.NET Identity can be of any type, includingString,int,long,Guid.
Also, did you notice that . the UpdatedAt and DeletedAt fields have nullable types? That’s because they’re null till the instance is updated, or deleted.
Mark as Soft Deletable
In my DbContext, I wanted to be able to mark as DbSet as Soft Deletable with:
builder.FilterSoftDeletedEntries<Company>();
And enforce that the audit fields were set with the right data, at the right time with an interface as simple as:
this.EnsureAudit<Guid?>(user?.Id);
Notice we use Guid? as the ApplicationUser's Id type? That’s because the CreatedBy,UpdatedBy and DeletedBy fields can be null.
Extending the DbContext
See this gist, on extending the *DbContext functionality to support this.
Writing the Auditable class
And this gist on the Auditable class.