Soft Deletes with Entity Framework Core 2 - Part 1

Entity Framework Core 2, already covered here, includes a cool feature called global filters. By leveraging global filters, we can apply restrictions automatically to entities, either loaded directly or through a collection reference. If we add this to shadow properties (in the case of relational databases, columns that exist in a table but not on the POCO model), we can do pretty cool stuff.

In this example, I am going to create a soft delete global filter to all entities in the model that implement a marker interface ISoftDeletable.

public interface ISoftDeletable
{
}
We just need to override the DbContext’s OnModelCreating method to automatically scan all known entities to see which implement this interface and then create the restriction automatically:
private const string _isDeletedProperty = "IsDeleted";
private static readonly MethodInfo _propertyMethod = typeof(EF).GetMethod(nameof(EF.Property), BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(typeof(bool));

private static LambdaExpression GetIsDeletedRestriction(Type type)
{
var parm = Expression.Parameter(type, "it");
var prop = Expression.Call(_propertyMethod, parm, Expression.Constant(_isDeletedProperty));
var condition = Expression.MakeBinary(ExpressionType.Equal, prop, Expression.Constant(false));
var lambda = Expression.Lambda(condition, parm);
return lambda;
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
if (typeof(ISoftDeletable).IsAssignableFrom(entity.ClrType) == true)
{
entity.AddProperty(_isDeletedProperty, typeof(bool));

modelBuilder
.Entity(entity.ClrType)
.HasQueryFilter(GetIsDeletedRestriction(entity.ClrType));
}
}

base.OnModelCreating(modelBuilder);
}
So, for each entity known from the context we add a shadow property called IsDeleted of type bool. Of course, needless to say, it must also exist on the database. The reason I’m making it a shadow property is to avoid people tampering with the entities, by setting or unsetting its value. This way, the restriction is always performed and it is invisible to us. After we create the property, we add a restriction to the entity’s type.
Simple, don’t you think? This way, if you want to enable or disable it for a number of entities, just have them implement the ISoftDeletable interface.

                             

8 Comments

  • Nice! But as far as I can tell, it doesn't prevent hard deletion... Is there a way to make it so that when you delete an entity, it actually updates the IsDeleted property?

  • tnx. realy usefull for me.

  • tnx. realy usefull for me.

  • I posted a similar post on my blog a few months ago. My implementation of Soft Delete using EF Core also *rewrite* deletes, so it prevents hard delete. This should answer Thomas levesque's question!
    https://www.meziantou.net/2017/07/10/entity-framework-core-soft-delete-using-query-filters

  • Hi Meziantou and Thomas Levesque! I wrote a second post on this: https://weblogs.asp.net/ricardoperes/soft-deletes-with-entity-framework-core-2-part-2. What do you think?

  • Meziantou: the only difference from your code and mine is that mine is dynamically applied, eg, I don't apply it on an entity by entity basis. ;-)

  • I would recommend to add an SQL index on the IsDeleted property. This can easily be added in OnModelCreating:

    modelBuilder.Entity(entity.ClrType).HasIndex(_isDeletedProperty).HasName($"IX_{_isDeletedProperty}");

  • Hi, Carsten! Thanks!

Add a Comment

As it will appear on the website

Not displayed

Your website