can linq expression be case insensitive
Asked Answered
S

4

13

i am leveraging this project to use jqgrid to filter and sort collections. The one missing feature is that this example is not doing case insensitive search which i need.

So if a user types in "Test" i want it to match with "TEST", "TeST", etc . .

i have code that looks like this:

            case WhereOperation.Equal:
                condition = Expression.Equal(memberAccessToString, filter);
                lambda = Expression.Lambda(condition, parameter);
                break;
            case WhereOperation.NotEqual:
                condition = Expression.NotEqual(memberAccessToString, filter);
                lambda = Expression.Lambda(condition, parameter);
                break;
            case WhereOperation.Contains:
                condition = Expression.Call(memberAccessToString,
                    typeof(string).GetMethod("Contains"),
                    Expression.Constant(value));
                lambda = Expression.Lambda(condition, parameter);
                break;

is there anyway to have these checks below being case insensitive so "Test" would equal "TEST"

Expression.NotEqual    
Expression.Equal
Expression.Call(memberAccessToString,
                    typeof(string).GetMethod("Contains"),
Shephard answered 14/3, 2011 at 22:44 Comment(6)
question is (even if it were possible) why would you need that? There seems to be a design problem - can you elaborate?Wellturned
@Wellturned - i am using this code to filter collections by some user input. there is no design problem here. The user might type in "test" or "test"Shephard
Would calling ToLower on both the filter string as well as the filtered value (maybe by using Expression.Call) be an option?Dylandylana
Couldn't you always format the user input to lowercase and then do the comparison?Jandy
@Dylandylana - maybe but i don't fully follow how this would work . .Shephard
@ooo: I've added an example as an answer.Dylandylana
A
9

Unfortunately the BCL does not have a Contains overload that allows you to specify case invariance. You will have to grab the correct overload of IndexOf as a workaround (checking to see if the result of IndexOf is greater than zero):

var methodInfo 
    = typeof(string)
        .GetMethod("IndexOf", 
            new[] { typeof(string), typeof(StringComparison) });

This MethodInfo accepts a string and a StringComparison which will allow you to specify StringComparison.OrdinalIgnoreCase if you wish.

Appointed answered 14/3, 2011 at 22:52 Comment(14)
@Andrew Hare - can you clarify your answer. i am a bit confused by your ressponse . . how would your code below do a case insensitive search ??Shephard
@ooo: I was just writing up an answer, but since Andrew has covered the most important bit, here's the rest of it: var callEx = Expression.Call(memberAccessToString, method, Expression.Constant(value), Expression.Constant(StringComparison.OrdinalIgnoreCase)); condition = Expression.NotEqual(callEx, Expression.Constant(-1));Missy
@Missy - where are you getting "method" from ??Shephard
@ooo: That's the methodInfo from Andrew's answer.Missy
@ooo - Sorry, it wouldn't. This code just gives you the MethodInfo you would need to do a case-invariant "contains" implementation yourself.Appointed
@Missy - Thanks for adding that :) Sorry I renamed the variable on you.Appointed
Actually, I should have been the one to check if the variable was named the same. :) Anyway, feel free to put this into the answer if you want, although you should know I haven't tested it.Missy
@ooo: As I mentioned in an earlier comment, condition = Expression.NotEqual(callEx, Expression.Constant(-1)); where callEx is what you just posted.Missy
@Andrew Hare / @Missy - Indexof() doesn't do Equals() but rather Contains() . . is there a different method i should be using to get an exact equalsShephard
@ooo: Yes, first.Equals(second, StringComparison.OrdinalIgnoreCase)Missy
@Missy - thanks. can you clarify how i would use the solution in your last comment with the above answer ??Shephard
@ooo: Since the signature is the same as what Andrew has posted, just replace "IndexOf" with "Equals" in the GetMethod call. As for the condition, do Expression.Equal(callEx, Expression.Constant(true))Missy
@Missy - i tried that but i am getting an exception "AmbiguousMatchException" Ambiguous match found. I assume this is because "Equals" is overloaded . . any thoughts ??Shephard
Just a note - this will work with regular linq, but not linq to sql or linq to entities. You will get the error "LINQ to Entities does not recognize the method 'Int32 IndexOf(System.String, System.StringComparison)' method, and this method cannot be translated into a store expression."Stultz
D
20

You could convert both values to lowercase before doing the comparison. Here's an example for Contains:

case WhereOperation.Contains:
    var toLower = Expression.Call(memberAccessToString,
                                  typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
    condition = Expression.Call(toLower,
                                typeof(string).GetMethod("Contains"),
                                Expression.Constant(value.ToString().ToLower()));
    lambda = Expression.Lambda(condition, parameter);
    break;

Note, however, that this won't pass the Turkey test.

Dylandylana answered 15/3, 2011 at 0:19 Comment(0)
A
9

Unfortunately the BCL does not have a Contains overload that allows you to specify case invariance. You will have to grab the correct overload of IndexOf as a workaround (checking to see if the result of IndexOf is greater than zero):

var methodInfo 
    = typeof(string)
        .GetMethod("IndexOf", 
            new[] { typeof(string), typeof(StringComparison) });

This MethodInfo accepts a string and a StringComparison which will allow you to specify StringComparison.OrdinalIgnoreCase if you wish.

Appointed answered 14/3, 2011 at 22:52 Comment(14)
@Andrew Hare - can you clarify your answer. i am a bit confused by your ressponse . . how would your code below do a case insensitive search ??Shephard
@ooo: I was just writing up an answer, but since Andrew has covered the most important bit, here's the rest of it: var callEx = Expression.Call(memberAccessToString, method, Expression.Constant(value), Expression.Constant(StringComparison.OrdinalIgnoreCase)); condition = Expression.NotEqual(callEx, Expression.Constant(-1));Missy
@Missy - where are you getting "method" from ??Shephard
@ooo: That's the methodInfo from Andrew's answer.Missy
@ooo - Sorry, it wouldn't. This code just gives you the MethodInfo you would need to do a case-invariant "contains" implementation yourself.Appointed
@Missy - Thanks for adding that :) Sorry I renamed the variable on you.Appointed
Actually, I should have been the one to check if the variable was named the same. :) Anyway, feel free to put this into the answer if you want, although you should know I haven't tested it.Missy
@ooo: As I mentioned in an earlier comment, condition = Expression.NotEqual(callEx, Expression.Constant(-1)); where callEx is what you just posted.Missy
@Andrew Hare / @Missy - Indexof() doesn't do Equals() but rather Contains() . . is there a different method i should be using to get an exact equalsShephard
@ooo: Yes, first.Equals(second, StringComparison.OrdinalIgnoreCase)Missy
@Missy - thanks. can you clarify how i would use the solution in your last comment with the above answer ??Shephard
@ooo: Since the signature is the same as what Andrew has posted, just replace "IndexOf" with "Equals" in the GetMethod call. As for the condition, do Expression.Equal(callEx, Expression.Constant(true))Missy
@Missy - i tried that but i am getting an exception "AmbiguousMatchException" Ambiguous match found. I assume this is because "Equals" is overloaded . . any thoughts ??Shephard
Just a note - this will work with regular linq, but not linq to sql or linq to entities. You will get the error "LINQ to Entities does not recognize the method 'Int32 IndexOf(System.String, System.StringComparison)' method, and this method cannot be translated into a store expression."Stultz
Z
4

You get it following way:

First Extend string class:

internal static class StringExtensions

    {
        public static bool ContainsExt(this String str, string val)
        {
            return str.IndexOf(val, StringComparison.InvariantCultureIgnoreCase) > -1 ? true : false;
        }
    }

Now write Expression call as

Expression.Call(null,
                    typeof(StringExtensions).GetMethod("ContainsExt", new Type[] { typeof(String), typeof(String) }),
                    new[] { memberAccessToString, Expression.Constant(value));
Zoril answered 22/7, 2011 at 15:6 Comment(0)
K
2
case WhereOperation.Contains:
     var callEx = Expression.Call(memberAccess, typeof(string).GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) }), Expression.Constant(value), Expression.Constant(StringComparison.OrdinalIgnoreCase));
     condition = Expression.NotEqual(callEx, Expression.Constant(-1));
     lambda = Expression.Lambda(condition, parameter);
     break;
Kenogenesis answered 13/6, 2012 at 11:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.