2019-11-22 13:12:45
Typically, soft deleting involves adding IsDeleted
columns to database tables that represents the model. As the name suggests this column contains a Boolean
value.
Considerations
New scenarios will be introduced when making data soft deletable. They should be considered carefully.
Two such scenarios are provided below:
Uniqueness
Re-evaluate database unique constraints and adjust as required.
On a side note. A good diagram of your database will come in handy here. I have used SketchBot.io of many occasions to create them.
Consider an Email
column in a User
table. Email
should to be unique. What happens when a user is soft-deleted and then a 'new' user, with the same email address, is added?
An answer to this scenario is to reactivate the record with an update and replace the non-unique fields. Be aware that any foreign keys from the reactivated record primary key will be assigned to the 'new' record with the same unique keys. So choose unique keys carefully.
Soft Delete in EF Core
When soft deleting, you might be tempted to add an IsDeleted
column to your table and model and update all of your queries to include a check for IsDeleted == false
.
That method will definitely work, but EF Core supports a more sophisticated approach called a QueryFilter
. The soft delete query filter will allow database querying as normal, but with the addition of automatically incorporating soft delete functionality.
To add a QueryFilter
, that utilizes a new IsDeleted
column, add the following code to the DBContext
class:
First, migrate your schema to include the IsDeleted
property. Next, override OnModelCreating
to include the following.
protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity<MyModel>().Property("IsDeleted"); builder.Entity<MyModel>().HasQueryFilter(m => EF.Property<bool>(m, "IsDeleted") == false); }
Then override both SaveChanges
and SaveChangesAsync
.
public override int SaveChanges() { UpdateSoftDeleteStatuses(); return base.SaveChanges(); } public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken)) { UpdateSoftDeleteStatuses(); return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } private void UpdateSoftDeleteStatuses() { foreach (var entry in ChangeTracker.Entries()) { switch (entry.State) { case EntityState.Added: entry.CurrentValues["IsDeleted"] = false; break; case EntityState.Deleted: entry.State = EntityState.Modified; entry.Reload();// We must reload because Ef sets all FKeys to Null when deleted!!! entry.CurrentValues["IsDeleted"] = true; break; } } }
This code intercepts any deleted queries and converts them to update queries that set the IsDeleted
column to true
. It also makes sure that any queries that insert any new data set the IsDeleted
column to false
.
EF Core will perform soft delete when you request deletion from code. Queries will ignore deleted data because the QueryFilter
only retains results for records with isDeleted == false
.
Accessing Deleted Records
Occasionally you may want to access the soft deleted records. This can be done by writing queries that include the IgnoreQueryFilters
property. See Query Filter docs for more detail.
Note. Any SQL queries will also ignore the query filters.