Audit trail and Soft Deletes in EF Core

CreatedAt, UpdatedAt, DeletedAt, CreatedBy, …, DeletedBy

Ikechi Michael
2 min readSep 29, 2019

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 theId field of theApplicationUser model from ASP.NET Identity can be of any type, including String, 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.

--

--

Ikechi Michael

I’ve learned I don’t know anything. I've also learned that people will pay for what I know. Maybe that's why they never pay.