2

I just spent some time to find how to return an IQueryable from a method... and I'm still wondering if it is the right way to do it.

Here is my repository class:

public class CarRepository : ICarRepository
{
    // Fake entities
    private IList<Car> _entities = new List<Car>()
    {
        new Car() { Brand = "Lamborghini", Name = "Huracán"},
        new Car() { Brand = "BMW", Name = "X6" }
    };

    // Allows deferred execution/further refinement
    public IQueryable<Car> FindByQueryable(Expression<Func<Car, bool>> predicate)
    {
        var query = _entities.Where(predicate.Compile()).AsQueryable();
        return query;
    }

    // Returning an IList or IEnumerable
    public IList<Car> FindBy(Expression<Func<Car, bool>> predicate)
    {
        return _entities.Where(predicate.Compile()).ToList();
    }
}

At first I thought that something like that should work, but it was not compiling:

    public IQueryable<Car> FindByQueryable(Expression<Func<Car, bool>> predicate)
    {
        var query = _entities.Where(predicate);
        return query;
    }

Am I right with the predicate.Compile() and .AsQueryable?

Thanks for you help! Bastien

PlaTyPuS
  • 385
  • 3
  • 15
  • 1
    I would rather return `IEnumerable` instead of `IQueryable`. – Alex Sikilinda Apr 25 '15 at 10:40
  • 1
    @AlexSikilinda IQueryable is a [better choice](http://stackoverflow.com/questions/2876616/returning-ienumerablet-vs-iqueryablet) for Databases. However I disapprove of this in general, I think a Repository that returns IQueryables is of low value (may as well just use the EF/LtSql etc linq provider directly and your abstractions will leak in palaces you don't expect [for example, in the case of EF, is it clear what data remains tracked?). – Nathan Cooper Apr 25 '15 at 10:44
  • It's ok like this. Leave the responsibility to the Dal layer which will interact with the repository. In the dal layer you then call ToList, when you have finished the total query construction. – Legends Apr 25 '15 at 11:01

2 Answers2

4

As it stand it makes no sense. If you want to use Queryable methods to remote queries into the database you must use expression trees. Using Compile converts a tree to a delegate which destroys this opportunity.

_entities must be an IQueryable<T> in order to target Queryable methods.

AsQueryable is a code smell that usually indicates the mistakes describe above. This is a fake queryable. It is in-memory (except if the source is really IQueryable; then it does a cast).

usr
  • 168,620
  • 35
  • 240
  • 369
2

The reason

var query = _entities.Where(predicate);

fails is because _entities only implements IEnumerable<Car>, not IQueryable<Car>. The Enumerable.Where extension method for IEnumerable<T> takes a Func<T, bool>. The Queryable.Where extension method for IQueryable<T> takes an Expression<Func<T, bool>>.

Instead of manually compiling your expression trees, you can move AsQueryable() a bit up:

var query = _entities.AsQueryable().Where(predicate);

As usr's answer rightly points out, this doesn't usually make sense, since it'll use local filtering, never any server-side filtering. However, there are exceptions where it does make sense, and your case may be one such exception: if you have multiple implementations of ICarRepository, some local, some remote, then it may make perfect sense to do it the way you're doing. You wouldn't want the user of ICarRepository to have to deal with the question of whether to use delegates or expression trees: the user should just use expression trees, your CarRepository can then pass those expression trees to some query provider, and that query provider can then choose whether to compile them to delegates, or to translate them to some other language such as SQL.