Implement Soft Deletes With Entity Framework’s Code First Approach

In order to maintain historical accuracy, data should not be truly deleted from a database. A soft delete can be used to signify a record is no longer valid, while persisting the data for any later use. Usually this is done by adding a “disabled” or “deleted” boolean column to each table on the database. This can be gracefully achieved by implementing base classes and data filters within your .NET Entity Framework code first solution.

An Example

I want to start recording some details about chickens and the eggs they lay. I’ll have a “Chicken” database table and a “ChickenEgg” table, with a basic one (chicken) to many (eggs) relationship. When an egg is used or goes bad, it should be soft deleted so that I know how many usable eggs I have access too. Similarly, if a chicken stops laying or dies, the chicken record would be disabled as well.

Both tables will need similar columns like “Id” and “IsDsiabled”, so I create a base class “BaseDomainObject” to hold these properties.

public class BaseDomainObject
{
	public int Id { get; set; }
	public bool IsDisabled { get; set; }
}

public class Chicken : BaseDomainObject
{
	public string Name { get; set; }
	public virtual List<ChickenEgg> ChickenEggs { get; set; }
}

public class ChickenEgg : BaseDomainObject
{
	public int ChickenId { get; set; }
	public string Color { get; set; }
}

Let’s populate this sample with some data. Here is the database initializer.

protected override void Seed(SoftDeleteExampleContext context)
{
	//chickens
	context.Chickens.Add(new Chicken
	{
		Name = "Fancy"
	});
	context.Chickens.Add(new Chicken
	{
		Name = "Roseanne"
	});
	context.SaveChanges();

	//eggs
	context.ChickenEggs.Add(new ChickenEgg
	{
		Color = "White",
		ChickenId = 1,
	});
	context.ChickenEggs.Add(new ChickenEgg
	{
		Color = "White",
		ChickenId = 1,
		IsDisabled = true, //we don't want to consider this egg
	});
	context.ChickenEggs.Add(new ChickenEgg
	{
		Color = "Brown",
		ChickenId = 2
	});

	context.SaveChanges();
}

A simple controller action returns this data as a JSON string.

[  
   {  
      "ChickenEggs":[  
         {  
            "ChickenId":1,
            "Color":"White",
            "Id":1,
            "IsDisabled":false
         },
         {  
            "ChickenId":1,
            "Color":"White",
            "Id":2,
            "IsDisabled":true
         }
      ],
      "Name":"Fancy",
      "Id":1,
      "IsDisabled":false
   },
   {  
      "ChickenEggs":[  
         {  
            "ChickenId":2,
            "Color":"Brown",
            "Id":3,
            "IsDisabled":false
         }
      ],
      "Name":"Roseanne",
      "Id":2,
      "IsDisabled":false
   }
]

However, this includes the disabled results that are unwanted.

...
{  
	"ChickenId":1,
	"Color":"White",
	"Id":2,
	"IsDisabled":true
}
...

Filter Out Disabled Records

The Entity Framework Dynamic/Global Filters plugin, found on NuGet, is a plugin that can help filter out any database row associated with a “BaseDomainObject”, if the “IsDisabled” property is true. Because every entity type we have inherits from BaseDomainObject, every query result uses this filter automagically and no further action is required.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
	modelBuilder.Filter("IsDisabled", (BaseDomainObject x) => x.IsDisabled, false);
}

The filtered results

[  
   {  
      "ChickenEggs":[  
         {  
            "ChickenId":1,
            "Color":"White",
            "Id":1,
            "IsDisabled":false
         }
      ],
      "Name":"Fancy",
      "Id":1,
      "IsDisabled":false
   },
   {  
      "ChickenEggs":[  
         {  
            "ChickenId":2,
            "Color":"Brown",
            "Id":3,
            "IsDisabled":false
         }
      ],
      "Name":"Roseanne",
      "Id":2,
      "IsDisabled":false
   }
]

The Code

You can download the Visual Studio solution for this example here.

Leave a Reply

Your email address will not be published. Required fields are marked *