Skip to content

Configuring Soft Delete

Jon P Smith edited this page Feb 22, 2023 · 8 revisions

The General Overview - configurations gave you an overview of how to create a configuration. This focuses on setting the properties in the configuration and provides examples of the Single Soft Delete and the Cascade Soft Delete configurations. The parts are

  1. Normal use
  2. Using shadow properties for the soft delete value
  3. Other configuration properties

NOTE: Another page, Multiple Query filters, deal with handling Query Filters with multiple filter parts.

1. Normal use

You have seen this in General Overview - configurations, but lets talk about the GetSoftDeleteValue and SetSoftDeleteValue (see code below).

  • The GetSoftDeleteValue contains an expession that can be used in a LINQ query, and also by the code to read an entity's value.
  • The SetSoftDeleteValue is an expression that the code can use to set the entity's value
public class ConfigSingleSoftDelete : SingleSoftDeleteConfiguration<ISingleSoftDelete>
{

    public ConfigSingleSoftDelete (SingleSoftDelDbContext context)
        : base(context)
    {
        GetSoftDeleteValue = entity => entity.SoftDeleted;
        SetSoftDeleteValue = (entity, value) => { entity.SoftDeleted = value; };
    }
}

The SoftDeleted is a 'bool' property, but it could be part of a [Flags] enum or something else. You can see an example of a configuration working with a Domain-Driven Design here.

If this was a cascade configuration (with extra IUserId handling) it would look like this

public class ConfigCascadeDeleteWithUserId : CascadeSoftDeleteConfiguration<ICascadeSoftDelete>
{

    public ConfigCascadeDeleteWithUserId(CascadeSoftDelDbContext context)
        : base(context)
    {
        GetSoftDeleteValue = entity => entity.SoftDeleteLevel;
        SetSoftDeleteValue = (entity, value) => { entity.SoftDeleteLevel = value; };
        OtherFilters.Add(typeof(IUserId), entity => ((IUserId)entity).UserId == context.UserId);
    }
}

Note that the the SoftDeleteLevel is a byte, not a bool

2. Using shadow properties for the soft delete value

The configuration seen above doesn't work with a shadow property, because you need a different expression for a query and the code accessing the value. To make this work you need to use the optional QuerySoftDeleteValue property in the configuration class.

public class ConfigSoftDeleteShadowDel : SingleSoftDeleteConfiguration<IShadowSoftDelete>
{
    public ConfigSoftDeleteShadowDel(SingleSoftDelDbContext context)
        : base(context)
    {
        GetSoftDeleteValue = entity => 
            (bool)context.Entry(entity).Property("SoftDeleted").CurrentValue;
        QuerySoftDeleteValue = entity => EF.Property<bool>(entity, "SoftDeleted");
        SetSoftDeleteValue = (entity, value) => 
            context.Entry(entity).Property("SoftDeleted").CurrentValue = value;
    }
}

You can see a cascade version of this here.

3. Other configuration properties

A series of properties to alter the retuned message.

There are three properties that are used when returning a status message - they are:

public string TextSoftDeletedPastTense { get; set; } = "soft deleted";

public string TextHardDeletedPastTense { get; set; } = "hard deleted";

public string TextResetSoftDelete { get; set; } = "reset the soft delete";

These are here because you might want to use different terms for your users. For example one of my clients used the term "delete" for a soft delete and "destroy" for a hard delete. That makes sense if you want your users to think they have deleted something, but actually you as an admin person can get it back.

bool ReadEveryTime property

NOTE: Advance feature, which is turned off by default. Only set the ReadEveryTime property to true if you understand what you need to do about collections.

You can gain a (small) performance on a SetCascadeSoftDelete call if you set the ReadEveryTime property to true. BUT you must ensure that your navigational collection properties in an entity class aren't set (i.e. they default to 'null'). Personally I leave collection properties as null because my code will fail if I forget to add a Include in my queries.

So, if you leave the collection properties to null if not loaded, then the SetCascadeSoftDelete knows it has to load any navigational collection property if the property is null. Otherwise, if the navigational collection property isn't null, then it doesn't need to load the collection because you have already loaded it.

NOTE: It only works on SetCascadeSoftDelete as on the reset you can't include soft deleted entities.