Thoughts from a tinkerer

Case insensitive string comparisons with LINQ Dynamic Query

One down-side to LINQ is that, out of the box, it’s geared towards knowing your query structure at compile-time. The values can be dynamic, of course, but it’s assumed that the structure of your query is static. For example, if you want to select a set of "Person" objects from the "People" collection where Person.FirstName starts with "Aar", you could write it as such:

var results = from person in People
where person.FirstName.StartsWith("Aar")
select person;

That’s all fine and good, but what about scenarios where you want to dynamically build up your query structure? In our client application we have address books (directories) that include the ability to filter them on any, or nearly any, column:

How would I accomplish this with LINQ? Not easily. Just ask Ayende or Rob Conery, both of whom have blogged about some of their adventures in advanced usage scenarios. Enter the LINQ Dynamic Query sample from Microsoft. As usual, ScottGu’s got a good write-up. In a nutshell, it’s a custom expression tree generator based on a limited (but useful) string-based query grammar. With Dynamic Query I could write the query above like this:

It solved my problem nicely. Almost. As with my example above about matching FirstName’s, let me ask: how often does a user enter an exact case-sensitive match for what they’re looking for? I can save you the trouble and tell you: it doesn’t matter. It’s an unacceptable requirement for a user to have to match something exactly. It’s already questionable that we don’t automatically use fuzzy matching algorithms.

So what I really want is to specify a StringComparison enum value on the call to "StartsWith":

Alas, this breaks. LINQ Dynamic Query doesn’t support enum values as parameters to methods. So I added it. I won’t redistribute the sample (I’m pretty sure I can’t, but I don’t care to anyway) so here’s what you need to do to add support for enum parsing. Note that I’ve only tested it with calls to string’s StartsWith(string, StringComparison) method. I don’t know what will happen if you sprinkle enum values in random places throughout your dynamic query. Work on My Machine, your mileage may vary, etc. etc. etc.

3. Add the definition for ParseEnumType. This little bit of nastiness is essentially doing a look-ahead to resolve a type name, since most of the parser’s rules are built to process more contextual information (such as a property name of a type, etc.) In our case, we need to attempt to match "Foo.Foo.Foo" to a type name, and if it doesn’t end up resolving, we need to reset the parser back to the beginning of "Foo" to continue parsing.