(Un)deleting me softly.
Introduction
There are occasions where you want that a delete of record (and the underlying object graph) does not result in a physical delete. Instead, the record (and the underlying object graph) is marked as deleted by means of a dedicated field “IsDeleted”, which gets the value true. This mechanism is called a soft delete.
Beth Massi has a great post on this subject (http://blogs.msdn.com/b/bethmassi/archive/2011/11/18/using-the-save-and-query-pipeline-to-archive-deleted-records.aspx). We’ll elaborate futher on this post.
The requirements
- we want to be able to apply a soft delete and make sure that the soft delete is propagated automatically to the underlying object graph;
- we want that entities can optionally participate in the soft delete ‘framework’, but if they participate, compile time checking will ensure that the entity has the “IsDeleted” field;
- we want to be able in a simple manner to activate filtering in such a way that entities which are soft deleted are not shown;
- we want to be able to “soft-undelete” an entity and -optionally- the underlying object graph. It make sense to make this feature permission dependent. Obviously, the usage of this feature goes together with de-activating the filtering on softdleleted items (in other words: when you want to undelete something, you need to see it).
The demo setting
To be able to test things in a decent manner, we make sure the object graph for our test setting is rich enough.
As you can see, we have both 1 to many structures as well as 1 to 0..1 relations.
How do we use the soft delete base infrastructure?
public partial class ApplicationDataService
{
partial void Customers_Filter(ref Expression<Func<Customer, bool>> filter)
{
filter = filter.NoSoftDeletesFilter<Customer>();
}
partial void Customers_Deleting(Customer entity)
{
entity.ApplySoftDelete();
}
partial void Orders_Deleting(Order entity)
{
entity.ApplySoftDelete();
}
partial void CustomerAddressDatas_Deleting(CustomerAddressData entity)
{
entity.ApplySoftDelete();
}
partial void OrderLInes_Deleting(OrderLIne entity)
{
entity.ApplySoftDelete();
}
}
So, server side, soft-delete for the above object graph is introduced with a few extension method calls.
The undelete functionaly is triggered client side:
partial void UndoSoftDelete_Execute()
{
this.Customers.SelectedItem.UndoSoftDelete();
}
Each entity type you want to let participate in the soft delete engine, should implement the following interface:
public interface IIsSoftDeletable
{
bool IsDeleted { get; set; }
}
The base infrastructure
Following 3 extension methods make up the soft delete engine:
Make sure to include following class in the common project !
public static class SoftDeleteExtensions
{
public static Expression<Func<T, bool>> NoSoftDeletesFilter<T>(this Expression<Func<T, bool>> filter) where T : IIsSoftDeletable
{
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "item");
PropertyInfo isDeletedPropInfo = typeof(T).GetProperty("IsDeleted", BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance);
MemberExpression isDeletedPropExpr = Expression.Property(paramExpr, isDeletedPropInfo);
ConstantExpression isTrue = Expression.Constant(true, typeof(bool));
BinaryExpression isNotDeleted = Expression.NotEqual(isDeletedPropExpr, isTrue);
return (Expression.Lambda<Func<T, bool>>(isNotDeleted, paramExpr));
}
public static void ApplySoftDelete(this IEntityObject entity)
{
if (entity is IIsSoftDeletable)
{
entity.Details.DiscardChanges();
entity.Details.Properties["IsDeleted"].Value = true;
}
else
{
throw new ArgumentException("You are trying to apply a filtering on soft deleted items, but the entity does not have an 'IsDeleted' field");
}
}
public static void UndoSoftDelete(this IEntityObject entity, bool recurse = false)
{
if (!TrySetIsDeletedProperty(entity))
return;
if (recurse)
{
IEnumerable<INavigationPropertyDefinition> navigationProperties = entity.Details.GetModel().NavigationProperties;
foreach (INavigationPropertyDefinition item in navigationProperties)
{
IAssociationEndDefinition associationEnd = item.Association.Ends.Where(e => e.EntityType.Name == entity.Details.EntitySet.Details.EntityType.Name).FirstOrDefault();
if (associationEnd != null)
{
var deleteRuleAttribute = associationEnd.Attributes.OfType<IDeleteRuleAttribute>().FirstOrDefault();
if (deleteRuleAttribute != null && deleteRuleAttribute.Action == DeleteAction.Cascade)
{
string navigationPropName = item.Name;
if (item.ToEnd.Multiplicity == AssociationEndMultiplicity.Many)
{
foreach (var navigationPropValue in (entity.Details.Properties[navigationPropName].Value as IEnumerable<IEntityObject>))
{
UndoSoftDelete(navigationPropValue);
}
}
else
{
UndoSoftDelete(entity.Details.Properties[navigationPropName].Value as IEntityObject);
}
}
}
}
}
}
private static bool TrySetIsDeletedProperty(IEntityObject entity)
{
bool result = false;
if (entity is IIsSoftDeletable)
{
entity.Details.Properties["IsDeleted"].Value = false;
result = true;
}
else
{
throw new ArgumentException("You are trying to apply a soft delete undo, but the entity does not have an 'IsDeleted' field");
}
return result;
}
}
As you can see, the softdelete filter is pretty advanced and is a combination of expression tree handling and reflective coding. I got some help of Justin Anderson via the LightSwitch forum (http://social.msdn.microsoft.com/Forums/en-US/lightswitch/thread/ff9c5bf4-001b-4d38-84b7-06af906eff14).
Note also the undo delete functionality is optionally recursive and if active we traverse the complete object graph.





Hi Paul
One more time nice article. Really useful
Thanks
You are welcome Rashmi.
Thank you Paul for another excellent article.
Thanks Dave !
[...] Van Bladel (@paulbladel) posted (Un)deleting me softly to his LightSwitch for the Enterprise blog on [...]
Beautiful… thanks Paul!
Hi Jewel,
Thanks a lot. Nice to hear from you.
paul.
[...] (Un)deleting me softly [...]