Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to build case insensitive strong typed LINQ query in c#?

I try to build extension method for IQuerable like this:

public static IQueryable<T> FilterByString<T>(this IQueryable<T> query, 
  Expression<Func<T, string>> propertySelector, 
  StringOperator operand, 
  string value)

that will generalize something like this:

query.Where(o => o.Name.ToLower().StartsWith(filterObject.Name.ToLower()))

into:

q.FilterByString(o => o.Name, filterObject.NameOperator, filterObject.Name)

to allow to set filtering operator (i.e. EndsWith, Contains).

I've seen on google some solutions to case sensitive filtering that requires using name of the property as a string instead of stron typed propertySelectorExpression. I've also seen solution for insensitive Contains() implementation using IndexOf() bu non of them seems to fit my needs.

For now I have this but it is not working (excpetiopn on "ToLower()" call):

static MethodInfo miTL = typeof(String).GetMethod("ToLower", System.Type.EmptyTypes);
static MethodInfo miS = typeof(String).GetMethod("StartsWith", new Type[] { typeof(String) });
static MethodInfo miC = typeof(String).GetMethod("Contains", new Type[] { typeof(String) });
static MethodInfo miE = typeof(String).GetMethod("EndsWith", new Type[] { typeof(String) });

public static IQueryable<T> FilterByString<T>(this IQueryable<T> query, 
  Expression<Func<T, string>> propertySelector, 
  StringOperator operand, 
  string value)
{
    Expression constExp = Expression.Constant(value.ToLower());

    Expression dynamicExpression = null;

    switch (operand)
    {
        case StringOperator.StartsWith:
            dynamicExpression = Expression.Call(propertySelector, miTL);
            dynamicExpression = Expression.Call(dynamicExpression, miS, constExp);
            break;
        case StringOperator.Contains:
            dynamicExpression = Expression.Call(propertySelector, miTL);
            dynamicExpression = Expression.Call(dynamicExpression, miC, constExp);
            break;
        case StringOperator.EndsWith:
            dynamicExpression = Expression.Call(dynamicExpression, miTL);
            dynamicExpression = Expression.Call(dynamicExpression, miE, constExp);
            break;
        default:
            break;
    }

    LambdaExpression pred = Expression.Lambda(dynamicExpression);

    return (IQueryable<T>)query.Provider.CreateQuery(Expression.Call(typeof(Queryable), 
        "Where", new Type[] {query.ElementType}, query.Expression, pred));
}

Any ideas?

like image 410
wlf84k Avatar asked Oct 13 '11 12:10

wlf84k


1 Answers

This should work, even if certainly not complete (nor elegant).

public static class LinqQueries
{
    private static MethodInfo miTL = typeof(String).GetMethod("ToLower", Type.EmptyTypes);
    private static MethodInfo miS = typeof(String).GetMethod("StartsWith", new Type[] { typeof(String) });
    private static MethodInfo miC = typeof(String).GetMethod("Contains", new Type[] { typeof(String) });
    private static MethodInfo miE = typeof(String).GetMethod("EndsWith", new Type[] { typeof(String) });

    public static IQueryable<T> FilterByString<T>(this IQueryable<T> query,
                                                  Expression<Func<T, string>> propertySelector,
                                                  StringOperator operand,
                                                  string value)
    {
        ParameterExpression parameterExpression = null;
        var memberExpression = GetMemberExpression(propertySelector.Body, out parameterExpression);
        var dynamicExpression = Expression.Call(memberExpression, miTL);
        Expression constExp = Expression.Constant(value.ToLower());
        switch (operand)
        {
            case StringOperator.StartsWith:
                dynamicExpression = Expression.Call(dynamicExpression, miS, constExp);
                break;
            case StringOperator.Contains:
                dynamicExpression = Expression.Call(dynamicExpression, miC, constExp);
                break;
            case StringOperator.EndsWith:
                dynamicExpression = Expression.Call(dynamicExpression, miE, constExp);
                break;
        }

        var pred = Expression.Lambda<Func<T, bool>>(dynamicExpression, new[] { parameterExpression });
        return query.Where(pred);

    }


    private static Expression GetMemberExpression(Expression expression, out ParameterExpression parameterExpression)
    {
        parameterExpression = null;
        if (expression is MemberExpression)
        {
            var memberExpression = expression as MemberExpression;
            while (!(memberExpression.Expression is ParameterExpression))
                memberExpression = memberExpression.Expression as MemberExpression;
            parameterExpression = memberExpression.Expression as ParameterExpression;
            return expression as MemberExpression;
        }
        if (expression is MethodCallExpression)
        {
            var methodCallExpression = expression as MethodCallExpression;
            parameterExpression = methodCallExpression.Object as ParameterExpression;
            return methodCallExpression;
        }
        return null;
    }

}

Will manage

xxx.FilterByString(m => m.Name,...)
xxx.FilterByString(m => m.Test.Name,...)
xxx.FilterByString(m => m.GetValue(),...)//GetValue() returns "string"

CAUTION : NullReferenceExceptions aren't managed (if Name is null, or Test is null, or Test.Name is null, or GetValue() returns null)...

like image 97
Raphaël Althaus Avatar answered Oct 23 '22 13:10

Raphaël Althaus