7

I have create a Generic Repository (Using EF 6.1.1), which I am using in several projects and it works very well. I have implemented a 'soft' delete feature, where we mark data as deleted, but do not actually remove it from the database.

I can then filter all my queries as all entities inherit from a base entity which has the IsDeleted property. This is all works nicely, but it obviously does not filter out any of the 'soft deleted' child entities.

I am unsure how to go about doing this in a generic way, as I dont want to have to over code a solution into every respoitory, that really defeats the reason for having a generic repo.

this is an example of my current Generic Repo

public sealed class MyRepository<T> : IRepository<T> where T : BaseEntity
{
    public String CurrentUser { get; set; }
    private readonly MyObjectContext context;

    private readonly Configuration configuration = ConfigurationManager.GetConfiguration();
    private IDbSet<T> entities;
    private IDbSet<T> Entities
    {
        get { return entities ?? (entities = context.Set<T>()); }
    }

    public MyRepository(MyObjectContext context, String userName = null)
    {
        this.context = context;

        var providerManager = new DataProviderManager(configuration);
        var dataProvider = (IDataProvider)providerManager.LoadDataProvider();
        dataProvider.InitDatabase();

        CurrentUser = userName;
    }

    public void Dispose()
    {
        //do nothing at the moment
    }

    public T GetById(Guid id)
    {
        return Entities.FirstOrDefault(x => x.Id == id && !x.IsDeleted);
    }

    public IQueryable<T> GetAll()
    {
        return Entities.Where(x => !x.IsDeleted);
    }

    public IQueryable<T> Query(Expression<Func<T, bool>> filter)
    {
        return Entities.Where(filter).Where(x => !x.IsDeleted);
    }

    public void Delete(T entity)
    {
        if (configuration.HardDelete)
        {
            HardDelete(entity);
        }
        else
        {
            SoftDelete(entity);
        }
    }

    private void HardDelete(T entity)
    {

        try
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            Entities.Attach(entity);
            Entities.Remove(entity);

        }
        catch (DbEntityValidationException ex)
        {
            var msg = string.Empty;

            foreach (var validationErrors in ex.EntityValidationErrors)
                foreach (var validationError in validationErrors.ValidationErrors)
                    msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);

            var fail = new Exception(msg, ex);
            throw fail;
        }
    }

    private void SoftDelete(T entity)
    {
        entity.IsDeleted = true;
        Update(entity);
    }
}

Any help on this would be great.

Thanks

JamesStuddart
  • 2,581
  • 3
  • 27
  • 45
  • So to be clear, "This is all works nicely, but it obviously does not filter out any of the 'soft deleted' child entities." You are referring to the entity's foreign key associated entities? So for instance, you want to pull all Non-Deleted users, but then when you lazy-load a "child" or "association" collection off of that user, you also want to filter out Deleted entities. Is that right?? – Adam Jul 02 '14 at 15:41
  • thats correct, by working nicely,I mean the general crud operations, but I just need this last stage to work, so when lazy loading it ignores the 'deleted' child entities. – JamesStuddart Jul 03 '14 at 07:46
  • Seems like you need to make changes in the ObjectContext to accomplish this did a little bit of reading this seems like the right way to go. http://blogs.claritycon.com/blog/2012/01/a-smarter-infrastructure-automatically-filtering-an-ef-4-1-dbset/ – Sign Aug 12 '14 at 13:29
  • There is a solution to the soft delete using entity framework over here http://stackoverflow.com/a/18985828/150342 – Colin Aug 15 '14 at 11:54

2 Answers2

7

Someone has built a global filter, you can try it and install it from nuget EntityFramework.Filters.

https://github.com/jbogard/EntityFramework.Filters

Here is an example how to use it.

public abstract class BaseEntity
{
    public int Id { get; set; }
    public bool IsDeleted { get; set; }
}
public class Foo : BaseEntity
{
    public string Name { get; set; }
    public ICollection<Bar> Bars { get; set; }
}
public class Bar : BaseEntity
{
    public string Name { get; set; }
    public int FooId { get; set; }
    public Foo Foo { get; set; }
}
public class AppContext : DbContext
{
    public DbSet<Foo> Foos { get; set; }
    public DbSet<Bar> Bars { get; set; }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Registers and configures it first.
        DbInterception.Add(new FilterInterceptor());
        var softDeleteFilter = FilterConvention.Create<BaseEntity>("SoftDelete", 
            e => e.IsDeleted == false); // don't change it into e => !e.IsDeleted
        modelBuilder.Conventions.Add(softDeleteFilter);
    }
}

Then you can enable it in your repository constructor or somewhere after db context instance is created because the filters are disabled by default.

using (var db = new AppContext())
{
    db.EnableFilter("SoftDelete");
    var foos = db.Foos.Include(f => f.Bars).ToArray(); // works on Include
}
using (var db = new AppContext())
{
    db.EnableFilter("SoftDelete");
    var foos = db.Foos.ToArray();
    foreach (var foo in foos)
    {
        var bars = foo.Bars; // works on lazy loading
    }
}
using (var db = new AppContext())
{
    db.EnableFilter("SoftDelete");
    var foos = db.Foos.ToArray();
    foreach (var foo in foos)
    {
        db.Entry(foo).Collection(f => f.Bars).Load(); // works on manual loading
    }
}

This filter is not needed anymore.

public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
    return Entities.Where(filter);//.Where(x => !x.IsDeleted);
}

As long as you have enabled it.

public MyRepository(MyObjectContext context, String userName = null)
{
    this.context = context;

    if (!configuration.HardDelete)
    {
        this.context.EnableFilter("SoftDelete");
    }
}
Yuliam Chandra
  • 14,494
  • 12
  • 52
  • 67
  • Does not work with the classes with a user generated key. If you want to replace a new `Foo` that has already been soft deleted, then are you hard out of luck. Soft delete columns are a personal afront to everything I hold dear. – Aron Aug 13 '14 at 11:02
  • What do you mean by `replace a new foo`? Update soft deleted Foo? You can disable the filter first, to get the deleted items, then perform the update and then re enabled the filters for select. And OP didn't mention anything about that, OP simply asked about filtering the deleted items even its child navigations. – Yuliam Chandra Aug 13 '14 at 11:06
  • You can easily try to insert an object that already exists but is soft deleted. I hate soft deletes because they break least surprise and creates a custom API – Aron Aug 13 '14 at 11:08
  • How can the front end gets the soft deleted items to be re-inserted if they have been filtered from back end? You meant insert a new object using the same primary key (that has been soft deleted) that's not an identity? – Yuliam Chandra Aug 13 '14 at 11:13
  • If the primary key an integer I think it should be an identity and even without soft delete mechanism inserting the same primary key violates the constraint. But since we are introducing soft delete, now it depends on the business logic / requirement, we can check it first `db.Foos.Find(xx)` or let it throw error message to let the user choose another id (not identity) – Yuliam Chandra Aug 13 '14 at 11:35
  • this has pointed me in the right direction, so I will award the points, thanks. – JamesStuddart Aug 18 '14 at 09:37
0

I was having the same problem but my method to solve was little different i used a genric base interface IGenricInterface with IsDeleted as property

public int DeletebyId(string Id)
 {
    var Ent = (IGenricInterface)_sitecontext.Set<TEntity>().Find(Id);
        Ent.IsDeleted = 1;
 }

and for hardDelete

 _sitecontext.Set<TEntity>().Remove(Ent);

This is on ID but offcourse you can do it on EnTity as well

Danish Shaikh
  • 161
  • 1
  • 5