Putting LINQ queries together, one piece at a time

Last week, good pal Rod Paddock asked me an interesting question about LINQ, and I think this may be useful to others (not that the answer may not already be out there somewhere, of course…).

The main scenario was that different queries had to be run depending on some options. The from and select parts of the query where pretty much the same; the parts that varied were the where and orderby ones. Here’s some code to illustrate a simplified scenario:

 

Collection<string> months = new Collection<string> { "January", "February", 
    "March", "April", "May", "June", "July", "August", "September", "October", 
    "November", "December" };

IEnumerable<string> query = null;

int? minNumberOfLetters = 5;

if (minNumberOfLetters.HasValue)
{
    query = from m in months
            where m.Length >= minNumberOfLetters
            select m;
}
else
{
    query = from m in months
            where m.Length >= minNumberOfLetters
            select m;
}

bool shouldOrderByLength = true;

if (shouldOrderByLength)
{
    query = from m in months
            where m.Length >= minNumberOfLetters
            orderby m.Length
            select m;
}

foreach (var item in query)
{
    Console.WriteLine(item);
}

The code should query only months whose name length is equals to or greater than minNumberOfLetter (in the sample above, that’d be 5), unless that variable holds a null, in which case all months should be returned. Also, the query should be ordered by the length of months names, in case shouldOrderByLength is true (obvioulsy, both minNumberOfLetter and shouldOrderByLenght wouldn’t be hard-coded, but instead, read from user input or whatever…).

The main problem with such code is that there’s a lot of redundancy in the queries. What if, for instance, instead of doing select m, we wanted to do select m.ToUpper()? We’d need to make that change in multiple places.

Queries can be broken up into smaller pieces. Queries don’t get executed until they’re iterated over, either by using in a foreach block, or calling ToList() on it (which uses a foreach internally…). That means we can tweak with them until right before the iteration happens.

So, we can refactor the previous code where we first declare the part that doesn’t vary in the query:

var query = from m in months select m;

Remember, the query above does NOT get executed just yet; we are just defining it, but not executing it (think of it as building a SQL query by concatenating strings, and eventually executing the query against the database).

Then we can add the where clause to the query if appropriate:

if (minNumberOfLetters.HasValue)
{
    query = query.Where((string m) => m.Length >= minNumberOfLetters);
}

Same with the orderby clause:

if (shouldOrderByLength)
{
    query = query.OrderBy((string m) => m.Length);
}

Whenever we’re done building the query, then we can iterate over it as needed.

Another cool thing we can do: say we need to order the query according to some specific logic; for example, if some variable is true we want to order the query by the month’s name, otherwise we want .to order it by the name’s length. That could be done something like this:

 

Func<string, object> orderBy = null;

if (orderByName)
{
    orderBy = (string m) => m;
}
else
{
    orderBy = (string m) => m.Length;
}

query = query.OrderBy(orderBy);

Basically, we declare a Func<string, object> variable that is going to hold the expression for ordering. In the if-block we assign a lambda expression to the variable accordingly. Finally, we add the call to OrderBy to the query, passing in the expression.

Here’s the complete final code, in case you want to mess with it:

 

Collection<string> months = new Collection<string> { "January", "February", "March", 
"April", "May", "June", "July", "August", "September", "October",
"November", "December" }; var query = from m in months select m; int? minNumberOfLetters = 5; if (minNumberOfLetters.HasValue) { query = query.Where((string m) => m.Length >= minNumberOfLetters); } bool shouldOrderByLength = true; if (shouldOrderByLength) { query = query.OrderBy((string m) => m.Length); } bool orderByName = true; Func<string, object> orderBy = null; if (orderByName) { orderBy = (string m) => m; } else { orderBy = (string m) => m.Length; } if (orderBy != null) { query = query.OrderBy(orderBy); } foreach (var item in query) { Console.WriteLine(item); }
  1. #1 by William on May 13, 2008 - 4:13 pm

    Claudio,
     
    Nice example, but unless I\’m missing something I think you mean "List" instead of "Collection" in the example source code. Unless Collection is a new keyword?
     
    Instead of Collection<string>
    shouldn\’t it be…
    List<string>
    ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: