Case insensitive string comparisons with LINQ Dynamic Query
LINQ rocks. It really does.
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:
var results = from person in People
select person;
results = results.Where("FirstName.StartsWith(\"Aar\")");
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":
var results = from person in People
select person;
results = results.Where("FirstName.StartsWith(\"Aar\", System.StringComparison.OrdinalIgnoreCase)");
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.
2. Crack open the Dynamic.cs source file. It’s scary, but you can do it. Modify it like so (I added the "if (ParseEnumType…"
Expression ParseIdentifier() {
ValidateToken(TokenId.Identifier);
object value;
if (keywords.TryGetValue(token.text, out value)) {
if (value is Type) return ParseTypeAccess((Type)value);
if (value == (object)keywordIt) return ParseIt();
if (value == (object)keywordIif) return ParseIif();
if (value == (object)keywordNew) return ParseNew();
NextToken();
return (Expression)value;
}
if (symbols.TryGetValue(token.text, out value) ||
externals != null && externals.TryGetValue(token.text, out value)) {
Expression expr = value as Expression;
if (expr == null) {
expr = Expression.Constant(value);
}
else {
LambdaExpression lambda = expr as LambdaExpression;
if (lambda != null) return ParseLambdaInvocation(lambda);
}
NextToken();
return expr;
}
// ADD THIS IF STATEMENT
if (ParseEnumType(out value))
{
Expression expr = Expression.Constant(value);
NextToken();
return expr;
}
if (it != null) return ParseMemberAccess(null, it);
throw ParseError(Res.UnknownIdentifier, token.text);
}
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.
bool ParseEnumType(out object value)
{
value = null;
ValidateToken(TokenId.Identifier);
Type enumType = null;
int position = token.pos;
string typeName = token.text;
while (enumType == null)
{
// Loop until we stop processing identifiers and/or dots
enumType = Type.GetType(typeName, false, true);
if (enumType == null)
{
NextToken();
if (token.id == TokenId.Dot)
{
typeName += token.text;
NextToken();
if (token.id == TokenId.Identifier)
{
typeName += token.text;
}
else
{
break;
}
}
else
{
break;
}
}
}
if ((enumType != null) && IsEnumType(enumType))
{
NextToken();
ValidateToken(TokenId.Dot, Res.DotExpected);
NextToken();
ValidateToken(TokenId.Identifier, Res.IdentifierExpected);
value = Enum.Parse(enumType, token.text, true);
return true;
}
else
{
SetTextPos(position);
NextToken();
}
return false;
}
4. Add an error "resource" string (but not really a true resource string) to the "Res" static class. We added a new condition, so we need an error message to match.
public const string DotExpected = "'.' expected";
Voila! Make sure your enum values are fully-qualified type names and you’ll be good to go.
Hopefully this works for you as well as it did for me, and I have to say I can’t believe I couldn’t find this on the ‘net, as I imagine this is a very common use-case.