Sunday 15 December 2019

IQueryable and Default Interface Methods

The discussion in the .Net world as to whether it's OK to have repositories returning IQueryable rather than IEnumerable has been rather present all these years. The common consensus is that it's not, and your repositories should not return an IQueryable. There are multiple reasons for this, but from an architecture point, probably this is one of the best I've read.

The point is that using IQueryable, you're asking for a query builder and not for a model. A query builder specifies how the query should be, how to get the data. Then why are we using a repository? Isn't the repo's job to know how to get the thing we want? We're using the repository because we want to specify what to get, not how to get it.

This goes along the idea of repositories having methods for "fat queries", so that the repository itself does the filtering, rather than returning a "collection" on which you apply filters. A similar approach is combining Repository and Specification pattern, your repository no longer have specific methods for each fat query, but you pass it over a specification with the query itself and it's the repository who performs the query.

While I agree with all the above, the idea of returning an IEnumerable and filtering it later, does not always seem like such a sin to me, after all we use Linq to Objects all the time. The big problem with returning an IEnumerable is that we would lose all the magic of IQueryable transforming our filters into a query and running it for us. The Linq methods for IEnumerable and IQueryable have been implemented as extension methods, and as we know this is a compiler trick, and method override and runtime resolution do not apply. If my IPersonRepository.GetItems returns and IEnumerable, my PersonSqlServerRepository.GetItems has to return an IEnumerable also in its signature (as C# lacks of Method return type covariance). We can respect that signature but return an IQueryable anyway, I mean:

IEnumerable GetItems()
{
 ...
 return dbContext.Persons; //returns and IQueryable
}

But the problem is that then, when doing myPersonsRepository.GetItems().Where(.....); the C# compiler would set a call to System.Linq.Enumerable.Where (rather than System.Linq.Queryable.Where), so we would be retrieving all the items from the Database, and then performing the filtering.

Now that C# 8 has added Default Interface Methods, if all the linq methods where added as default methods to IEnumerable and IQueryable the whole thing would change. As the method resolution would be done at runtime, calling Where in the above case would end up going to the IQueryable.Where, and generating the consequent sql query. This would be really sweet, but I've not heard so far about any plans of "copy-pasting" (you can not just move them due to backwards compatibility) the methods in System.Linq.Queryable into IQueryable as default interface methods.

No comments:

Post a Comment