Apr 142012
 
Share...Tweet about this on Twitter5Share on Facebook0Share on Google+0Share on StumbleUpon0Share on LinkedIn0Flattr the authorPin on Pinterest0Share on Reddit0Share on TumblrDigg this

I am currently writing a prototype for an application so I thought it would be good to start using a different ORM than NHibernate and the Entity Framework Code First was the next “obvious choice”. Code First is particularly interesting because it allows using POCO object, which fits perfectly with my programming style, as I try to follow as much as possible Domain Driven Development where the well written domain objects have a very important role.

Please consider downloading a fully working example that covers all the code mentioned in the post:
EntityFramework_GlobalFilter_v1

Recently, while developing a Data Access Layer I came up to an interesting problem: How to apply global automatic filtering to a certain entity in Entity Framework Code First? In other words, how to make sure that an entity is always filtered with a pre-set filter without specifying the “where” clause in every query, which is potentially cumbersome and error prone. At the beginning it seemed pretty much a simple thing to do, but then it turned to be much more difficult, so I would like to share with you my findings.
In my particular case I need to filter the data based on the user currently logged in, but for the sake of simplicity I am proposing you a different example.

I am familiar with the NHibernate Filter feature, but I couldn’t find anything similar in Entity Framework Code First, so after some internet search two posts have came in very handy, and I would like to thank the guys for offering their solutions, try checking here and here.

Why am I writing this post then? The solution proposed in the given links works very well in most cases but I found out that there some issues while loading related object in queries by using the Include() method like in the example below:

var query = context.Categories.Include("Products");

so, I am posting the corrected version of the FilteredDbSet class which does the magic, and I will describe here one of the ways of how to implement the global filter.

I assume that you are familiar with Entity Framework Code First, so I won’t go too much into details, but just concentrate on the particularities!

Domain Model

In this example I am going to use a pretty straightforward object model:

public class Category
{
    public string CategoryId { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public virtual ICollection Products { get; set; }
}

public class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }
    public string CategoryId { get; set; }
    public bool IsActive { get; set; }
    public virtual Category Category { get; set; }
}

DBContext setup

For simplicity of the exercise, the EF Code first is configured to create the database, so that I may avoid the creation of the mapping logic. I wouldn’t personally use this in production code.

ProductContext class has two interesting points to be mentioned:

  1. Instead of using the standard DbSet to return the Entity data, we will use the interface IDbSet for the entities we want to apply the filter to.
  2. There is a method ApplyFilter() that gives the possibility of injecting some predefined filters in the context.
public class ProductContext : DbContext
{
    static ProductContext()
    {
        Database.SetInitializer(new DropCreateDatabaseIfModelChanges());
    }

    public void ApplyFilters(IList<IFilter<ProductContext>> filters)
    {
        foreach (var filter in filters)
        {
            filter.DbContext = this;
            filter.Apply();
        }
    }

    public IDbSet Categories { get; set; }
    public IDbSet Products { get; set; }
}

Creation of the Filters

For reusability, I created an interface called IFilter that every filter should implement:

public interface IFilter<T> where T : DbContext
{
    T DbContext { get; set; }
    void Apply();
}

Example of a filter that makes sure that every query made on Products or Categories entities would only return the records that contain “Active” products.

public class ActiveProductsFilter :  IFilter<ProductContext> 
{
    public ProductContext DbContext  {  get;  set;  }

    public void Apply()
    {
        DbContext.Products = new FilteredDbSet(DbContext, d => d.IsActive &&  d.Category.IsActive);
        DbContext.Categories = new FilteredDbSet(DbContext, c => c.Products.All(p => p.IsActive));
    }
}

IDbSet Filtering Implementation

The most important point in the solution is the implementation of the FilteredDbSet. This class hasn’t been written by me, I’ve just corrected few places that would make it work with the above mentioned “Include” command, so the credit goes to the original authors.
FilteredDbSet makes sure that the where clause is registered permanently and executed for each query. This is the full implementation.

public class FilteredDbSet<TEntity> : IDbSet<TEntity>, IOrderedQueryable<TEntity>, IOrderedQueryable, IQueryable<TEntity>, IQueryable, IEnumerable<TEntity>, IEnumerable, IListSource
    where TEntity : class
{
    private readonly DbSet<TEntity> _set;
    private readonly Action<TEntity> _initializeEntity;
    private readonly Expression<Func<TEntity, bool>> _filter;

    //---------------------------------------------------------------------------
    public FilteredDbSet(DbContext context)
        : this(context.Set<TEntity>(), i => true, null)
    {
    }

    //---------------------------------------------------------------------------
    public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter)
        : this(context.Set<TEntity>(), filter, null)
    {
    }

    //---------------------------------------------------------------------------
    public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
        : this(context.Set<TEntity>(), filter, initializeEntity)
    {
    }

    //---------------------------------------------------------------------------
    public Expression<Func<TEntity, bool>> Filter
    {
        get { return _filter; }
    }

    //---------------------------------------------------------------------------
    public IQueryable<TEntity> Include(string path)
    {
        return _set.Include(path).Where(_filter).AsQueryable();
    }

    //---------------------------------------------------------------------------
    private FilteredDbSet(DbSet<TEntity> set, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
    {
        _set = set;
        _filter = filter;
        MatchesFilter = filter.Compile();
        _initializeEntity = initializeEntity;
    }

    //---------------------------------------------------------------------------
    public Func<TEntity, bool> MatchesFilter
    {
        get;
        private set;
    }

    //---------------------------------------------------------------------------
    public IQueryable<TEntity> Unfiltered()
    {
        return _set;
    }

    //---------------------------------------------------------------------------
    public void ThrowIfEntityDoesNotMatchFilter(TEntity entity)
    {
        if (!MatchesFilter(entity))
            throw new ArgumentOutOfRangeException();
    }

    //---------------------------------------------------------------------------
    public TEntity Add(TEntity entity)
    {
        DoInitializeEntity(entity);
        ThrowIfEntityDoesNotMatchFilter(entity);
        return _set.Add(entity);
    }

    //---------------------------------------------------------------------------
    public TEntity Attach(TEntity entity)
    {
        ThrowIfEntityDoesNotMatchFilter(entity);
        return _set.Attach(entity);
    }

    //---------------------------------------------------------------------------
    public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity
    {
        var entity = _set.Create<TDerivedEntity>();
        DoInitializeEntity(entity);
        return (TDerivedEntity)entity;
    }

    //---------------------------------------------------------------------------
    public TEntity Create()
    {
        var entity = _set.Create();
        DoInitializeEntity(entity);
        return entity;
    }

    //---------------------------------------------------------------------------
    public TEntity Find(params object[] keyValues)
    {
        var entity = _set.Find(keyValues);
        if (entity == null)
            return null;

        // If the user queried an item outside the filter, then we throw an error.
        // If IDbSet had a Detach method we would use it...sadly, we have to be ok with the item being in the Set.
        ThrowIfEntityDoesNotMatchFilter(entity);
        return entity;
    }

    //---------------------------------------------------------------------------
    public TEntity Remove(TEntity entity)
    {
        ThrowIfEntityDoesNotMatchFilter(entity);
        return _set.Remove(entity);
    }

    //---------------------------------------------------------------------------
    /// <summary>
    /// Returns the items in the local cache
    /// </summary>
    /// <remarks>
    /// It is possible to add/remove entities via this property that do NOT match the filter.
    /// Use the <see cref="ThrowIfEntityDoesNotMatchFilter"/> method before adding/removing an item from this collection.
    /// </remarks>
    public ObservableCollection<TEntity> Local
    {
        get { return _set.Local; }
    }

    //---------------------------------------------------------------------------
    IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
    {
        return _set.Where(_filter).GetEnumerator();
    }

    //---------------------------------------------------------------------------
    IEnumerator IEnumerable.GetEnumerator()
    {
        return _set.Where(_filter).GetEnumerator();
    }

    //---------------------------------------------------------------------------
    Type IQueryable.ElementType
    {
        get { return typeof(TEntity); }
    }

    //---------------------------------------------------------------------------
    Expression IQueryable.Expression
    {
        get
        {
            return _set.Where(_filter).Expression;
        }
    }

    //---------------------------------------------------------------------------
    IQueryProvider IQueryable.Provider
    {
        get
        {
            return _set.AsQueryable().Provider;
        }
    }

    //---------------------------------------------------------------------------
    bool IListSource.ContainsListCollection
    {
        get { return false; }
    }

    //---------------------------------------------------------------------------
    IList IListSource.GetList()
    {
        throw new InvalidOperationException();
    }

    //---------------------------------------------------------------------------
    void DoInitializeEntity(TEntity entity)
    {
        if (_initializeEntity != null)
            _initializeEntity(entity);
    }

    //---------------------------------------------------------------------------
    public DbSqlQuery<TEntity> SqlQuery(string sql, params object[] parameters)
    {
        return _set.SqlQuery(sql, parameters);
    }
}

Putting it all together

In order to execute the query, we have first to instantiate the ProductContext and specify the filter.

var context = new ProductContext();
context.ApplyFilters(new List<IFilter<ProductContext>>()
                     {
                         new ActiveProductsFilter()                                         
                     });

var categories = context.Categories.Include("Products").ToList();

Please note that adding any extra filters will work just fine, so this is a perfectly valid code:

var categories = context.Categories.Include("Products")
                        .Where(c=>c.Name.StartsWith("U")).ToList();

Conclusion

The proposed solution is a very simple way of aggregating filters in one place so that there is less code repetition. My hope is that Microsoft sooner or later will include this feature in one of the upcoming versions of Entity Framework.

Share...Tweet about this on Twitter5Share on Facebook0Share on Google+0Share on StumbleUpon0Share on LinkedIn0Flattr the authorPin on Pinterest0Share on Reddit0Share on TumblrDigg this

My name is Zoran Maksimovic a Software Developer and Solution Architect. I'm interested in Software Development, Object-Oriented Design and Software Architecture all this especially bound to the Microsoft.NET platform. Feel free to contact me or know more in the about section

  15 Responses to “Entity Framework Code First – Applying Global Filters”

  1. No need to filter at controller action level you can simply put that inside Context constructor and it will work…….

    public CustomerContext()
    : base(“DefaultConnection”)
    {
    this.ApplyFilters(new List<IFilter>()
    {
    new GeneratorBase.MVC.Models.CustomerContext.ActiveCustomersFilter()
    });
    }

  2. So, I was trying to use this. I think it is a very useful method of filtering but it seems that the code presented doesnt work when chaining multiple filters. I had in my app a need for filtering based on EmployeeId. So I set up a filter for the entities I needed to filter and everything worked as expected. Then I added a filter for NotDeleted, which, as you may guess, filtered based on a IsDeleted bool in the entities. The problem is, it seems that the last filter wins.

    The reason for this is simple. In each filter the DbContext is passed into the FilteredDbSet. The constructor for the FilteredDbSet get the DbSet by context.Set() and passes that to the private constructor. Here is the issue: the DbContext Set will always be unfiltered. So the second filter in the chain just filters the original values.

    So now the fix:
    Its pretty easy, I changed the:
    private readonly DbSet _set;
    to
    private readonly IDbSet _set;

    the
    private FilteredDbSet(DbSet set, Expression<Func> filter, Action initializeEntity)
    to
    private FilteredDbSet(IDbSet set, Expression<Func> filter, Action initializeEntity)

    and added a public constructor that takes an IDbSet instead of DbContext
    public FilteredDbSet(IDbSet set, Expression<Func> filter)
    : this(set, filter, null)
    {
    }

    So, this way in the filter instead of passing in the DbContext as a parameter, pass in DbContext.TEntity, this way a previously filtered Set will not lose its filter.

    One side effect though, the method SqlQuery on FilteredDbSet will not work as IDbSet doesnt expose this. I just commented the whole method out, someone else might do some more work to preserve its function, but I feel if Im going to run a raw sql query it will be a) an edge case and b) I can manually filter in the sql statement.

    Hope this helps someone.

    Jason

  3. So far, nothing. I’m working at this too. It’s sad EF doesn’t support native soft delete.

  4. Nice code, I almost thought that it was solving my problem.

    But it isn’t. There is problem with this kind of filtering because of the include.

    The filters that you apply are
    DbContext.Products = new FilteredDbSet(DbContext, d => d.IsActive && d.Category.IsActive);
    DbContext.Categories = new FilteredDbSet(DbContext, c => c.Products.All(p => p.IsActive));

    But, the filter on category will also exclude categories which have a mix of active / not active products.
    In reality, such a mix is often the case.

    When you remove the categoy filter then suddenly those inactive products are back when retrieving categories, including products

    context.Categories.Include(c => c.Products).ToList();

    Do you have a solution for that problem ?

    • Hi Bart,
      I will take a look on this as it seems a serious flaw.

      • Has this problem ever been resolved?

        • Anyone ever get this working? I was just writing a reply explaining the same thing when I realized someone already posted it. The query stated:

          var categories = context.Categories.Include(“Products”).ToList();

          returns only categories that have all active products, excluding categories that have both active and inactive products.

          How do we return all categories, along with only their active products?

  5. Regarding the text at the top of this article: “POCO object, which fits perfectly with my programming style, as I try to follow as much as possible Domain Driven Development”, Entity Framework’s implementation of POCO is not domain-driven or agile given the following constraints:

    • The class and property names must align with the Entity Data Model.

    • All Entity Data Model properties in the model entity must be represented in the class.

    • Hi Curt,
      Thanks for commenting.
      Actually none of your claims are true, as if you check the Entity Framework Code First version of the framework (which i usually refer to), EF Code First perfectly fits with the POCO definitions.

      I agree with you that this was not the case with previous versions of EF, where one would be forced to use the designer. With EF Code First you can configure your existing domain objects to map to your database. Yes, there are some limitations as it doesn’t support all of the scenarios that NHibernate for instance would support, but yet most of the scenarios are possible. So, your class names do not need to be called in a specific way, not the class properties need to follow any pattern.

      I invite you to read some documents around Entity Framework Code First.

      Cheers,
      Zoran

  6. [...] –3. Business Layer: c# class library MyApplication.Business Following FilteredDbSet Class courtesy: Zoran Maksimovic http://www.agile-code.com/blog/entity-framework-code-first-applying-global-filters/ [...]

  7. glass spigot…

    it is my first comment on this blog and to start with I would like to thank you for the great quality information, which I found in this and all previous topics , it really helped me very much. I will definitely put this website on my rss reader Also, …

  8. What’s about implementation without having to explicitly define IDbSet. Let say that the system use directly the Set() method to get the Entity. Is there a way to implement the filter?

    • Hi Patrick,
      For what am I aware of, it is not possible to create a global filter in such a way. It is possible to introduce a where clause for a given entity, but the whole catch is when you start using the Include method, and at that point without the code above your filter won’t work.
      Please let me know if in the meantime you found something as I would be interested in the solution!

  9. Definitely, what a splendid site and instructive posts, I definitely will bookmark your blog.Best Regards!

Leave a Reply

heiderman-erin@mailxu.com ruesch_cindi@mailxu.com