score:2

Accepted answer

The solution you're looking for is to require entities have an IsDeleted value of false:

modelBuilder.Entity<Company>()
    .Map( emc => emc.Requires( "IsDeleted" ).HasValue( false ) );

Now only companies with IsDeleted == false will be retrieved from the DB

Update from comment:

modelBuilder.Entity<Company>()
    .Map( emc => 
    {
        emc.MapInheritedProperties();
        emc.Requires( "IsDeleted" ).HasValue( false );
    } )
    .Ignore( c => c.IsDeleted );

Update: test code which was successful (helper methods found here):

[Table("EntityA")]
public partial class EntityA
{
    public int EntityAId { get; set; }
    public string Description { get; set; }


    public virtual EntityB PrimaryEntityB { get; set; }

    public virtual EntityB AlternativeEntityB { get; set; }

    public bool IsDeleted { get; set; }
}

[Table("EntityB")]
public partial class EntityB
{
    public int EntityBId { get; set; }
    public string Description { get; set; }

    [InverseProperty("PrimaryEntityB")]
    public virtual ICollection<EntityA> EntityAsViaPrimary { get; set; }
    [InverseProperty( "AlternativeEntityB" )]
    public virtual ICollection<EntityA> EntityAsViaAlternative { get; set; }
}

public partial class TestEntities : DbContext
{
    public TestEntities()
        : base("TestEntities")
    {
        Database.SetInitializer( new DatabaseInitializer() );
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<EntityA>()
            .Map( emc =>
                {
                    emc.Requires( "IsDeleted" ).HasValue( false );
                } )
                .Ignore( a => a.IsDeleted );
    }

    public override int SaveChanges()
    {
        foreach( var entry in this.ChangeTracker.Entries<EntityA>() )
        {
            if( entry.State == EntityState.Deleted )
            {
                SoftDelete( entry );
            }
        }

        return base.SaveChanges();
    }

    private void SoftDelete( DbEntityEntry entry )
    {
        var entityType = entry.Entity.GetType();

        var tableName = GetTableName( entityType );
        var pkName = GetPrimaryKeyName( entityType );

        var deleteSql = string.Format( "update {0} set IsDeleted = 1 where {1} = @id",
            tableName,
            pkName );

        Database.ExecuteSqlCommand( deleteSql, new SqlParameter( "@id", entry.OriginalValues[ pkName ] ) );

        entry.State = EntityState.Detached;
    }

    private string GetPrimaryKeyName( Type type )
    {
        return GetEntitySet( type ).ElementType.KeyMembers[ 0 ].Name;
    }

    private string GetTableName( Type type )
    {
        EntitySetBase es = GetEntitySet( type );

        return string.Format( "[{0}].[{1}]",
            es.MetadataProperties[ "Schema" ].Value,
            es.MetadataProperties[ "Table" ].Value );
    }
    private EntitySetBase GetEntitySet( Type type )
    {
        ObjectContext octx = ( ( IObjectContextAdapter )this ).ObjectContext;

        string typeName = ObjectContext.GetObjectType( type ).Name;

        var es = octx.MetadataWorkspace
                        .GetItemCollection( DataSpace.SSpace )
                        .GetItems<EntityContainer>()
                        .SelectMany( c => c.BaseEntitySets
                                        .Where( e => e.Name == typeName ) )
                        .FirstOrDefault();

        if( es == null )
            throw new ArgumentException( "Entity type not found in GetTableName", typeName );

        return es;
    }

    public DbSet<EntityA> EntityAs { get; set; }
    public DbSet<EntityB> EntityBs { get; set; }
}

Application code:

class Program
{
    static void Main(string[] args)
    {
        using( var db = new TestEntities() )
        {
            var a0 = new EntityA()
                {
                    EntityAId = 1,
                    Description = "hi"
                };

            var a1 = new EntityA()
                {
                    EntityAId = 2,
                    Description = "bye"
                };

            db.EntityAs.Add( a0 );
            db.EntityAs.Add( a1 );

            var b = new EntityB()
            {
                EntityBId = 1,
                Description = "Big B"
            };

            a1.PrimaryEntityB = b;

            db.SaveChanges();

            // this prints "1"
            Console.WriteLine( b.EntityAsViaPrimary.Count() );

            db.EntityAs.Remove( a1 );

            db.SaveChanges();

            // this prints "0"
            Console.WriteLine( b.EntityAsViaPrimary.Count() );
        }

        var input = Console.ReadLine();
    }
}

score:0

You can do soft delete like this:

  1. In OnModelCreating add an IsDeleted discriminator to every entity that can be soft deleted
  2. Override SaveChanges and find all the entries to be deleted
  3. Run SQL on these entries to set the IsDeleted discriminator then set their state to "detached"
  4. Change any unique indexes to ignore any soft deleted records

You can find working code at this answer: How to soft delete using Entity Framework Code First

And that code was picked up by this Blog: http://netpl.blogspot.com/2013/10/soft-delete-pattern-for-entity.html

score:0

Four years later, I finally stumbled on a library that does exactly what I want.

EntityFramework.DynamicFilter.

modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);

The above snippet ensures that the soft-deleted items are not retrieved. This affects both direct queries (db.Set<Foo>().ToList()) and indirect loadaed entities db.Set<Foo>().Include(e => e.Bars).ToList()), thus completely hiding the soft deleted entities from view.


For completeness' sake, I've combined this with an override to SaveChanges() which converts hard deletes to soft deletes before committing to the database.

This means that developers are able to safely use hard delete logic, and they'll never even realize that the context is using soft deletes. They don't need to know, they don't need to care, they can't forget to implement it the right way, they never need to write a check for the IsDeleted flag.


Related Articles