Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call Contains() method in LINQ to Entities Expression on a type other than String

I am trying to implement searching/filtering in a grid that allows the user to create filter conditions that involve a column, an operation, and a value.

Example: Column1 Contains "somevalue"

The following code works fine when the chosen column contains string types:

case WhereOperation.Contains:
    Expression condition = Expression.Call(
       memberAccess, //memberAccess is a MemberExpression of the property bound to the column.
       typeof(string).GetMethod("Contains"),
       Expression.Constant(value.ToString())); // value is what we are checking to see if the column contains.
    LambdaExpression lambda = Expression.Lambda(condition, parameter);
    break;

However, when the property the column is bound to is not of type string (i.e. an Int), this fails because the type Int does not have a "Contains" method. How can I first get the ToString() value of memberAccess before calling "Contains" on it?

Note 1: The type of the property that "memberAccess" represents is unknown at compile time.
Note 2: The lambda expression ends up being used in a LINQ 2 entities query which cannot explicitly handle ToString(). (See what I tried below)

This is one solution I tried but it fails at the time of the LINQ expression evaluation because LINQ 2 entities doesn't support ToString():

case WhereOperation.Contains:
    Expression stringValue = Expression.Call(memberAccess,
       typeof(object).GetMethod("ToString"));
    Expression condition = Expression.Call(
       stringValue, 
       typeof(string).GetMethod("Contains"),
       Expression.Constant(value.ToString()));
    LambdaExpression lambda = Expression.Lambda(condition, parameter);
    break;
like image 851
Tom Avatar asked Mar 25 '23 00:03

Tom


1 Answers

Yeah... you cant use ToString in Linq to Entities, because of security reasons. Can't remember where i read it. It is suported in Linq2Sql though. But there is SqlFunctions.StringConvert that you can use.

I made simple extension for you:

public static class ExpressionExtensions
{
    public static Expression<Func<T, bool>> AddContains<T>(this LambdaExpression selector, string value)
    {
        var mi = typeof (string).GetMethods().First(m => m.Name == "Contains" && m.GetParameters().Length == 1);


        var body = selector.GetBody().AsString();
        var x = Expression.Call(body, mi, Expression.Constant(value));

        LambdaExpression e = Expression.Lambda(x, selector.Parameters.ToArray());
        return (Expression<Func<T, bool>>)e;
    }

    public static Expression GetBody(this LambdaExpression expression)
    {
        Expression body;
        if (expression.Body is UnaryExpression)
            body = ((UnaryExpression)expression.Body).Operand;
        else
            body = expression.Body;

        return body;
    }

    public static Expression AsString(this Expression expression)
    {
        if (expression.Type == typeof (string))
            return expression;

        MethodInfo toString = typeof(SqlFunctions).GetMethods().First(m => m.Name == "StringConvert" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(double?));
        var cast = Expression.Convert(expression, typeof(double?));
        return Expression.Call(toString, cast);
    }
}

usage:

        IQueryable<Foo> seed = ...;
        Expression<Func<Foo, int>> selector = x => x.Id;
        var expression = selector.AddContains<Foo>("3");
        seed.Where(expression);  
like image 76
maxlego Avatar answered Mar 26 '23 14:03

maxlego