Course contentsShow
← C#
Lesson 1116 of 1,90810. Entity Framework Core ModelingFree lesson

Implementing Soft Deletes

Create standardized infrastructure for filtering out deleted rows across all queries.

In this lesson, you will learn how to automate soft deletes in Entity Framework Core so that your application automatically ignores "deleted" rows without manually appending a Where clause to every query.

The Concept: Global Query Filters

A "Soft Delete" replaces a hard DELETE operation with an UPDATE, typically setting an IsDeleted flag or a DeletedAt timestamp. The challenge is ensuring no developer forgets to exclude those records during retrieval. Manually filtering these out in every Repository or Service is error-prone.

Instead, we use Global Query Filters. These are defined in the OnModelCreating method of your DbContext. Once defined, EF Core automatically applies this filter to every query execution for that entity.

Implementation

First, ensure your entities implement a common interface, like ISoftDelete:

public interface ISoftDelete {
    bool IsDeleted { get; set; }
}

public class Product : ISoftDelete {
    public int Id { get; set; }
    public bool IsDeleted { get; set; }
}

Next, configure the filter in your DbContext:

protected override void OnModelCreating(ModelBuilder modelBuilder) {
    foreach (var entityType in modelBuilder.Model.GetEntityTypes()) {
        // Automatically apply filter to any entity implementing ISoftDelete
        if (typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType)) {
            modelBuilder.Entity(entityType.ClrType)
                .HasQueryFilter(ConvertFilterExpression(entityType.ClrType));
        }
    }
}

private LambdaExpression ConvertFilterExpression(Type type) {
    var parameter = Expression.Parameter(type, "e");
    var property = Expression.Property(parameter, nameof(ISoftDelete.IsDeleted));
    var body = Expression.Equal(property, Expression.Constant(false));
    return Expression.Lambda(body, parameter);
}

Why this works

By iterating through the model metadata, we create a unified infrastructure for all soft-deletable entities. Because the filter is injected into the query expression tree at the DbContext level, even complex joins and navigation properties will respect the IsDeleted == false rule. If you ever need to access deleted data (e.g., for an administrative audit), you can simply append .IgnoreQueryFilters() to your LINQ statement to bypass this safety net temporarily.

Key Takeaway: Global Query Filters transform soft-delete logic from a manual coding requirement into an automated infrastructure rule, ensuring data integrity across your entire application.