Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic Repository Pattern Soft Delete

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

like image 847
JamesStuddart Avatar asked Jul 02 '14 15:07

JamesStuddart


1 Answers

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");
    }
}
like image 170
Yuliam Chandra Avatar answered Nov 15 '22 02:11

Yuliam Chandra