Firstly, we need to identify which entities are the ones that should support the soft-delete behavior. In order to do that we can use the following interface:
public interface ISoftDeleteEntityWe can use partial classes in order to implement the interface, so there is no need to modify the auto-generated classes.
{
bool Deleted { get; set; }
}

public partial class Customer : ISoftDeleteEntityFinally, we need to override the SaveChanges of the ObjectContext in order to get the deleted entities and run the soft-delete logic.
{
}
public partial class DatabaseEntitiesAs you can see in the code snippet, first of all we are obtaining all the entities that are going to be deleted and that implement the ISoftDeleteEntity interface. After that, we are changing their state to modified and setting the Deleted property.
{
public override int SaveChanges(SaveOptions options)
{
var deletedEntities = GetDeletedEntities();
SoftDelete(deletedEntities);
return base.SaveChanges(options);
}
private List<ISoftDeleteEntity> GetDeletedEntities()
{
return ObjectStateManager
.GetObjectStateEntries(EntityState.Deleted)
.Select(entry => entry.Entity)
.OfType<ISoftDeleteEntity>()
.ToList();
}
private void SoftDelete(List<ISoftDeleteEntity> deletedEntities)
{
deletedEntities.ForEach(e =>
{
ObjectStateManager.ChangeObjectState(e, EntityState.Modified);
e.Deleted = true;
});
}
}
| Note: If the changes were not detected yet (WCF DataServices scenario), you should call the DetectChanges method before obtaining the deleted entities. The attached source code shows how to do it. |
You can download the sample code from here.
14 comments:
an what is the difference between do this and make an update?
Although the result is the same, using this technique you get the following advantages:
- Business Objects / Repositories don't need to know whether the entity should be soft-deleted or not.
- There is less chances of mistakes because there is no code that can delete the object.
Good work Jorge, thanks.
Hi Jorge,
Great post btw. Just one question: How would you leave the posibility of having both operations available? With this change implemented we won't be able to actually delete the record, the only way this could happen is if we do not implement the interface on the entity. Any ideas?
Thanks.
Hi mmfoscar,
It depends on how and who has the knowledge about whether it should be soft-delete or hard-delete.
One option could be to add another property that specifies the type of delete. Something like this:
public interface ICustomDeleteEntity
: ISoftDeleteEntity
{
DeleteType DeleteType { get; set; }
}
public enum DeleteType
{
Soft,
Hard
}
In order to set this property, you can create an extension method for the IDbSet:
public static class DbSetExtensions
{
public static void Remove<TEntity>(this IDbSet<TEntity> dbSet, TEntity entity, DeleteType type) where TEntity : class, ICustomDeleteEntity
{
entity.DeleteType = type;
dbSet.Remove(entity);
}
}
And then you will need to change the GetDeletedEntities implementation to something like this:
private List<ISoftDeleteEntity> GetDeletedEntities()
{
return ObjectStateManager
.GetObjectStateEntries(EntityState.Deleted)
.Select(entry => entry.Entity)
.OfType<ICustomDeleteEntity>()
.Where(entity => entity.DeleteType == DeleteType.Soft)
.Cast<ISoftDeleteEntity>()
.ToList();
}
I haven't run the code, so you will probably need to fix it, but I hope it helps you to understand the idea.
Kind Regards,
Jorge
Hi again,
With Jorge's solution:
If you have an entity like
Student
name
classId
then if you call the
MyEntities.Students.DeleteObject(student);
even before call MyEntities.SaveChanges();
You loose all bindings on student, I mean It becomes student.class = null
Thus softdelete only keeps name.
did you come up with a nice approach to filtering out deleted items when loading?
Hi, also have you got an idea for dealing with navigation properties that have been soft deleted?
You can automatically filter deleted items if you use a descriminator
eg. modelBuilder.Entity().Map(m => m.Requires("IsDeleted").HasValue(false));
However this means you can't have a deleted flag on the entity and must set it using raw sql. Disabling the PluralizingTableNameConvention makes it easier to figure out the table name
There is a problem with soft delete which I havent figured out a good way to solve yet.
Lets say you delete an Enity which has a property set to Unique in you database.
When soft deleting this object, it will still be available in the database table and you will not be able to insert a new object into the database with the same unique property.
How do you handle these issues?
Add a DeleteDate field to the database. Set it to 1970-01-01 by default. When you soft delete an entity set the DeleteDate to the current date. Then put your unique constraint on the column you require to be unique + the deleted flag + the delete date, like so:
ALTER TABLE XXX ADD CONSTRAINT UC_XXX_UniqueColumnName UNIQUE (UniqueColumnName,Deleted,DeleteDate)
This would allow you to delete the entry multiple times. For example, you could have entries like this:
UniqueColumn:Deleted:DeleteDate
-------------------------------
UniqueValue:False:1970-1-1 (active)
UniqueValue:True:2012-2-2 (deleted on 2/2)
UniqueValue:True:2012-2-4 (deleted on 2/4)
If you are losing foreign key or navigation property data, you should add the entry.ApplyOriginalValues(entry.Entity) to unflag them for deletion.
StackOverFlow Answer
Hi Jorge,
I am having a reference problem when i want to SoftDelete an entity which has a collection of another entity which are already soft deleted.
Any ideas?
Post a Comment