Whether you come from a functional, procedural, or object oriented background, it’s always a good idea to promote code reuse as much as possible. One of the best ways to have reusable LINQ queries is to write them in a composable fashion.
Put simply, composable methods are those that you can chain together to build the desired functionality out of smaller parts. Take the following for example:
Although its a bit contrived, if you wanted a slightly different result set, you would have to recreate most of the query logic. If, however, you used composable methods like the following you only have to create the new logic that’s required instead of reinventing the wheel:
Creating composable methods is actually quite easy, essentially all you need to do is make sure the return type of a method is the same as the input type. For instance, the following could be the signature for the FilterByType method:
As any good little programmer should do, most of us use the most basic type possible for our method signatures. The problem is, that could be the exact opposite of what should be done. Although IQueryable<T> inheirets from IEnumerable<T>, it is often the case that using IQueryable<T> is more appropriate.
Why is this an issue? Well, the LINQ queries are evaluated differently on IQueryables than on IEnumerables. More specifically, queries on IEnumerable collections are evaluated immediately while those on IQuerable collections aren’t evaluated until you enumerate over the results (which includes converting the collection to an array, list, or any other type of IEnumerable).
To see what is actually going on, we will go through some examples and log the SQL that is actually passed to SQL Server, for which we will use the DataContext.Log property. If we were to run the SQL-like LINQ query above you would find the SQL to be something like the following:
This is what you would expect to see; however, if you were to use the extension method we defined above, FilterByType, you may be surprised to see:
So what happened? Well, since the extension method we wrote was defined for an IEnumerable, the .Net framework chose the IEnumerable version of LINQ’s Where extension method. Consequently, all records from the Vehicle table will be returned and loaded as LINQ objects into an IEnumerable collection. At this point each object would be enumerated over to see if they meet the qualifications of the predicate we passed to the Where method. As you might expect, this is far from performant and could become a large bottleneck within your application.
So, now what? Well the obvious answer would be to simply change the extension method to be for IQueryable<Vehicle> instead of IEnumerable<Vehicle>. The problem with this is now you have an API that is less than friendly to use if you have a non-IQueryable collection, for instance a List<Vehicle>. Instead, I would propose that you have an extension method defined for both the IQueryable and IEnumerable collections, after all this is what the .Net framework does. Your comment now would naturally be, “Wait, I thought we were trying to avoid duplicate code”. Well, you’re right; we are trying to avoid duplicate code, which is why we will be employing the AsQueryable extension method within the IEnumerable version of the extension method.
The runtime cost of using AsQueryable is virtually nothing and now you can use your logic for both IEnumerable and IQueryable collections. You now also have the opportunity to optimize the queries for a particular type of collection if possible.