Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call Ignore Case for Contains Method using a generic LINQ Expression

I am using below code for Generic Filter, any search text passed but the contains method is Case sensitive, how can I write to ignore case.

public static class QueryExtensions
{
    public static IQueryable<T> Filter<T>(this IQueryable<T> query, string search)    
    {           
        var properties = typeof(T).GetProperties().Where(p => 
                /*p.GetCustomAttributes(typeof(System.Data.Objects.DataClasses.EdmScalarPropertyAttribute),true).Any() && */
                p.PropertyType == typeof(String));        

        var predicate = PredicateBuilder.False<T>();
        foreach (var property in properties )
        {
           predicate = predicate.Or(CreateLike<T>(property,search));
        }
        return query.AsExpandable().Where(predicate);
    }
    private static Expression<Func<T,bool>> CreateLike<T>( PropertyInfo prop, string value)
    {       
        var parameter = Expression.Parameter(typeof(T), "f");
        var propertyAccess = Expression.MakeMemberAccess(parameter, prop);                    
        var like = Expression.Call(propertyAccess, "Contains", null, Expression.Constant(value,typeof(string)));

        return Expression.Lambda<Func<T, bool>>(like, parameter);       
    }

}
like image 549
ineffable p Avatar asked Jul 25 '13 10:07

ineffable p


2 Answers

Instead of calling String.Contains, call String.IndexOf with a case insensitive StringComparison parameter. Then compare its result with 0, with the Expression.GreaterThanOrEqual expression. You need to provide the extra parameter in your Expression.Call as an Expression.Constant.

You can decide to hardcode one of the case-insensitive StringComparison options, or export it as a parameter of the Filter method, allowing users to decide whether they want case-insensitive search or not.

You can do something like this:

    private static Expression<Func<T, bool>> CreateLike<T>(PropertyInfo prop, string value)
    {
        var parameter = Expression.Parameter(typeof(T), "f");
        var propertyAccess = Expression.MakeMemberAccess(parameter, prop);

        var indexOf = Expression.Call(propertyAccess, "IndexOf", null, Expression.Constant(value, typeof(string)),Expression.Constant(StringComparison.InvariantCultureIgnoreCase));
        var like=Expression.GreaterThanOrEqual(indexOf, Expression.Constant(0));
        return Expression.Lambda<Func<T, bool>>(like, parameter);
    }

or, with the StringComparison parameter

    private static Expression<Func<T, bool>> CreateLike<T>(PropertyInfo prop, 
        string value, 
        StringComparison comparison=StringComparison.InvariantCultureIgnoreCase)
    {
        var parameter = Expression.Parameter(typeof(T), "f");
        var propertyAccess = Expression.MakeMemberAccess(parameter, prop);

        var indexOf = Expression.Call(propertyAccess, "IndexOf", null, 
            Expression.Constant(value, typeof(string)),
            Expression.Constant(comparison));
        var like=Expression.GreaterThanOrEqual(indexOf, Expression.Constant(0));
        return Expression.Lambda<Func<T, bool>>(like, parameter);
    }

By using a default value for comparison you avoid creating two overloads for the same job.

like image 89
Panagiotis Kanavos Avatar answered Oct 16 '22 00:10

Panagiotis Kanavos


You could try using String.IndexOf instead.

string x,y = string.Empty;
x.IndexOf(y,0,x.Length, StringComparison.CurrentCultureIgnoreCase) > -1

As it has a StringComparison parameter.

This would return an integer

var like = Expression.Call(propertyAccess, "IndexOf", null, Expression.Constant(value, typeof(string)), Expression.Constant(StringComparison.CurrentCultureIgnoreCase,typeof(StringComparison)));
like image 22
ywm Avatar answered Oct 15 '22 23:10

ywm