4

I'm using EF 5 Database first approach in my MVC application. all of my tables uses a Field called Deleted which is a boolean field to mark a record is deleted.

I'm trying to get rid of the requirement of having to check Deleted == false every time I query my database. The very straightforward way of doing this is to use a conditional mapping in the edmx file where EF always return data that are not deleted. That's all good.

But the problem of doing this condition mapping is that, when I want to allow the user to delete some record for e.g Address from their address book I don't have access to Delete field from EF as I used it in the conditional mapping and therefore I have to look for another option to allow user to delete a record.

The way I thought is to create a stored proc that handle the delete query and call it when I want to delete the record.

Is there a better way of doing this? Is it possible to make the Delete field accessible even it is used in the conditional mapping?

Amila
  • 3,711
  • 3
  • 26
  • 42

1 Answers1

14

I have a working solution for Soft Delete in Entity Framework Code First that may help.

The key is that you add a discriminator to every model that you want to be able to soft delete. In code first that is done like this:

modelBuilder.Entity<Foo>().Map(m => m.Requires("IsDeleted").HasValue(false));

This makes it invisible to the context and therefore you have to do the deletes using sql.

If this is the equivalent of your "conditional mapping" in Database First then one way to modify the sql is to override SaveChanges and run sql from there:

public override int SaveChanges()
{
   foreach (var entry in ChangeTracker.Entries()
             .Where(p => p.State == EntityState.Deleted 
             && p.Entity is ModelBase))//I do have a base class for entities with a single 
                                       //"ID" property - all my entities derive from this, 
                                       //but you could use ISoftDelete here
    SoftDelete(entry);

    return base.SaveChanges();
}

private void SoftDelete(DbEntityEntry entry)
{
    var e = entry.Entity as ModelBase;
    string tableName = GetTableName(e.GetType());
    Database.ExecuteSqlCommand(
             String.Format("UPDATE {0} SET IsDeleted = 1 WHERE ID = @id", tableName)
             , new SqlParameter("id", e.ID));

    //Marking it Unchanged prevents the hard delete
    //entry.State = EntityState.Unchanged;
    //So does setting it to Detached:
    //And that is what EF does when it deletes an item
    //http://msdn.microsoft.com/en-us/data/jj592676.aspx
    entry.State = EntityState.Detached;
}

Method used to Get Table Name explained here

That is the way I used to do it. Probably irrelevant to your Database First approach in EF5, but I have now moved to doing it in stored procedures. EF6 Code First generates CreateStoredProcedure calls in Migration files. I replace these with this.CreateDeleteProcedure("dbo.Foo_Delete", "[dbo].[Foos]"); - which is a call to my own extension method:

public static class MigrationExtensions
{
    internal static string DeleteSqlFormat
    {
        //I also hard delete anything deleted more than a day ago in the same table
        get { return "DELETE FROM {0} WHERE IsDeleted = 1 AND DATEADD(DAY, 1, DeletedAt) < GETUTCDATE(); UPDATE {0} SET IsDeleted = 1, DeletedAt = GETUTCDATE() WHERE ID = @ID;"; }
    }

    internal static void CreateDeleteProcedure(this DbMigration migration, string procName, string tableName)
    {
        migration.CreateStoredProcedure(
                        procName,
                        p => new
                        {
                            ID = p.Int(),
                        },
                        body:

                            string.Format(MigrationExtensions.DeleteSqlFormat, tableName)

                    );
    }

}
Community
  • 1
  • 1
Colin
  • 22,328
  • 17
  • 103
  • 197
  • That's the word I was looking for, "Soft Delete". Yes, this seems to be a good solution for code first. Most probably, I'm going to create a dynamic query in a stored proc and use stored procedure mapping in edmx and map the stored procedure I create to Delete. So when I call Delete on the context, it will call that Delete stored procedure I map automatically. – Amila Oct 09 '13 at 11:54
  • Impressive, after searching for a working solution, I've just found one. There were some minor issues in getting the `GetTableName` to work, the version you posted in the other post needs some minor tweaks to even compile. But this doesn't change the fact that, indeed, this soft delete approach works like a charm. – Wiktor Zychla Oct 11 '13 at 11:00
  • @WiktorZychla Thank-you. I think I neglected to include where I got _ObjectContext from in that post. Corrected now. – Colin Oct 11 '13 at 12:21
  • 1
    I've just made a blog post comparing your working approach to an "almost working approach" and giving you the full credit for this one: http://netpl.blogspot.com/2013/10/soft-delete-pattern-for-entity.html – Wiktor Zychla Oct 11 '13 at 12:28
  • @WiktorZychla I like it. I did think that the bit that goes to meta-data was fragile around inheritance, and I never thought about caching meta-data. That was one of the reasons I switched to stored procedures. Now I just need to find out how to get Add-Migration to use my stored procedure sql automatically....More about `GetTableName` here http://stackoverflow.com/a/18964974/150342 – Colin Oct 11 '13 at 13:06
  • If you want me to introduce your full name in my blog entry, just let me know. – Wiktor Zychla Oct 11 '13 at 18:22