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
IsDeleted
as 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
Id
field of theApplicationUser
model 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.