Sunday, 11 January 2015

AsQueryable

The discussions as to whether Repositories should return IEnumerable or IQueryable has been hot and present for a long while. There are pros and cons, and I don't think I can add anything particularly useful on what path to follow. However, if you decide to go the IQueryable path, I've found something of some interest related to it.

One problem with returning IQueryable could seem to be, "and what do I do with my Mocks?" The typical case is having some mock repositories returning List<Entity>, so what can you do to comply with that contract that expects IQueryable<Entity>? Well, the BCL itself provides all you need, so no effort required, all you have to do is invoke AsQueryable in your collections and your normal IEnumerable will be turned into an IQueryable!

Seems interesting to take a fast look at how this thing works, but first let's do quick recap of how the different Linq flavours work.

  • Linq to Objects is implemented as just a set of extension methods in the System.Linq.Enumerable class.
  • As discussed in this previous post, the Extension Methods for Parallel Linq are located in the System.Linq.ParallelEnumerable class.
  • The Extension Methods for IQueryable are located in the System.Linq.Queryable class (that along with the other classes is located in the System.Core assembly)
  • As explained in this old post (time flies...) an IQueryable Linq Provider is based on implementing Sytem.Linq.IQueryProviderT> and having in your Sytem.Linq.IQueryable classes a Provider property pointing to it and an Expression property referencing to an Expression Tree that represents the Query (this means that when you chain Linq calls on an IQueryable, this Expression property will be the composition of those queries).
  • The IQueryable<T> interface implements IEnumerable<T>. If you have a class implementing IQueryable but want to use the Enumerable methods on it you can just call the Enumerable.AsEnumerable extension method (or just do a casting, that is just what that method does, as we know Extension Methods resolution is purely done at compile time). When you do this with your DataBase related IQueryable collection you'll be losing all the Sql construction magic that you LinqProvider probably implements under the covers, and you'll end up with a "select all" query and the filtering will be done in that huge in memory collection. Sometimes you need this cause you have a complex query and can't write a Expression Tree for it or your Provider can't convert it into SQL statements.

So, what happens when you invoke AsQueryable (that is an Extension Method in the System.Linq.Queryable class expecting an IEnumerable parameter)?

Well, it can not be too complex. The IQueryProvider provided by the new IQueryable object will receive an expression Tree representing the query, so it occurs to me that if it keeps a reference to the IEnumerable object, it can compile the Expression Tree into a normal delegate and use it with any of the System.Linq.Enumerable extension methods.

Let's go a bit through the Framework code (out of habit I still feel more comfortable going through the decompiled classes shown by ILSpy that using the Reference Source code...) to see how it is implemented. AsQueryable returns a System.Linq.EnumerableQuery object. Same as some objects implement both IEnumerable and IEnumerator (they know how to iterate themselves), this class implements both IQueryable and IQueryProvider (it knows how to query itself). As MSDN says:

Represents an IEnumerable collection as an IQueryable data source.

This EnumerableQuery object contains a reference (the Enumerable property) to the original IEnumerable object. When we invoke "Linq queries" on it (the System.Linq.Queryable extension methods), they'll retrieve the provider (that in this case we've seen that is the EnumerableQuery object itself) and will invoke the CreateQuery method on it (source.Provider.CreateQuery), that in turn returns another EnumerableQuery object. As we do so, the Expression tree for the current Query is composed to the Expression Tree representing previous queries, this way (extract from System.Linq.Enumerable.Where):
return source.Provider.CreateQuery(Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[]
	{
		typeof(TSource)
	}), new Expression[]
	{
		source.Expression,
		Expression.Quote(predicate)
	}));

Finally, when the data represented by this query is really needed, the GetEnumerator method in EnumerableQuery will get invoked:

private IEnumerator GetEnumerator()
{
	if (this.enumerable == null)
	{
		EnumerableRewriter enumerableRewriter = new EnumerableRewriter();
		Expression body = enumerableRewriter.Visit(this.expression);
		Expression>> expression = Expression.Lambda>>(body, null);
		this.enumerable = expression.Compile()();
	}
	return this.enumerable.GetEnumerator();
}

which will finally compile and execute that Expression that has been composed all over the chain of queries.

No comments:

Post a Comment