views:

695

answers:

2
+2  Q: 

dynamic linq Like

How to write a dynamic linq method for Like clause.

For.: the reference for Dynamic linq orderby is http://stackoverflow.com/questions/41244/dynamic-linq-orderby.

I am looking for a similar one for dynamic Like clause.

I have the following extension methods for like:

public static IQueryable<T> Like<T>(this IQueryable<T> source, string propertyName, string keyword)
    {
        var type = typeof(T);
        var property = type.GetProperty(propertyName);
        var parameter = Expression.Parameter(type, "p");
        var propertyAccess = Expression.MakeMemberAccess(parameter, property);
        var constant = Expression.Constant("%" + keyword + "%");
        MethodCallExpression methodExp = Expression.Call(null, typeof(SqlMethods).GetMethod("Like", new Type[] { typeof(string), typeof(string) }), propertyAccess, constant);
        Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(methodExp, parameter);
        return source.Where(lambda);
    }

The above method gives an error

Method 'Boolean Like(System.String, System.String)' cannot be used on the client; it is only for translation to SQL.

The other method which is somehow modified from http://stackoverflow.com/questions/41244/dynamic-linq-orderby:

public static IQueryable<T> ALike<T>(this IQueryable<T> source, string property, string keyword)
    {
        string[] props = property.Split('.');
        Type type = typeof(T);
        ParameterExpression arg = Expression.Parameter(type, "x");
        Expression expr = arg;

        foreach (string prop in props)
        {
            // use reflection (not ComponentModel) to mirror LINQ
            PropertyInfo pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }
        var constant = Expression.Constant("%" + keyword + "%");
        MethodCallExpression methodExp = Expression.Call(null, typeof(SqlMethods).GetMethod("Like", new Type[] { typeof(string), typeof(string) }), expr, constant);
        Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
        LambdaExpression lambda = Expression.Lambda(delegateType, methodExp, arg);
        object result = typeof(Queryable).GetMethods().Single(
                method => method.IsGenericMethodDefinition
                        && method.GetGenericArguments().Length == 2
                        && method.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(T), type)
                .Invoke(null, new object[] { source, lambda });
        return (IQueryable<T>)result;
    }

The above method gives an error:

Expression of type 'System.Boolean' cannot be used for return type 'System.String'

Any ideas on this?

+2  A: 

Are you aware of SqlMethods.Like ?

David B
This gives the same error mentioned above: "Method 'Boolean Like(System.String, System.String)' cannot be used on the client; it is only for translation to SQL."
Ryan Elkins
@Ryan Elkins, you want the functionality of "like", but do not want to call a database server?
David B
+2  A: 

Something like:

static void Main() {
    using(var ctx= new DataClasses1DataContext()) {
        ctx.Log = Console.Out;
        var qry = ctx.Customers.WhereLike("CompanyName", "a%s");

        Console.WriteLine(qry.Count());
    }
}
static IQueryable<T> WhereLike<T>(this IQueryable<T> source,
        string propertyOrFieldName, string pattern) {
    var param = Expression.Parameter(typeof(T), "row");
    var body = Expression.Call(
        null,
        typeof(SqlMethods).GetMethod("Like",
            new[] { typeof(string), typeof(string) }),
        Expression.PropertyOrField(param, propertyOrFieldName),
        Expression.Constant(pattern, typeof(string)));
    var lambda = Expression.Lambda<Func<T, bool>>(body, param);
    return source.Where(lambda);
}
static IQueryable<T> WhereLike<T>(this IQueryable<T> source,
        string propertyOrFieldName, string pattern, char escapeCharacter) {
    var param = Expression.Parameter(typeof(T), "row");
    var body = Expression.Call(
        null,
        typeof(SqlMethods).GetMethod("Like",
            new[] { typeof(string), typeof(string), typeof(char) }),
        Expression.PropertyOrField(param, propertyOrFieldName),
        Expression.Constant(pattern, typeof(string)),
        Expression.Constant(escapeCharacter,typeof(char)));
    var lambda = Expression.Lambda<Func<T, bool>>(body, param);
    return source.Where(lambda);
}

You might also consider making it more reusable:

static void Main() {
    using(var ctx= new DataClasses1DataContext()) {
        ctx.Log = Console.Out;
        var qry1 = ctx.Customers.WhereInvoke<Customer, string>(
            "CompanyName", s => s.Contains("abc"));
        Console.WriteLine(qry1.Count());

        var qry2 = ctx.Customers.WhereInvoke<Customer, string>(
            "CompanyName", s => s.StartsWith("abc"));
        Console.WriteLine(qry2.Count());

        var qry3 = ctx.Customers.WhereInvoke<Customer, string>(
            "CompanyName", s => s.EndsWith("abc"));
        Console.WriteLine(qry3.Count());
    }
}
static IQueryable<TSource> WhereInvoke<TSource, TValue>(
        this IQueryable<TSource> source,
        string propertyOrFieldName,
        Expression<Func<TValue, bool>> func) {
    var param = Expression.Parameter(typeof(TSource), "row");
    var prop = Expression.PropertyOrField(param, propertyOrFieldName);
    if(prop.Type != typeof(TValue)) {
        throw new InvalidOperationException("The property must be " + typeof(TValue).Name);
    }
    var body = Expression.Invoke(func, prop);
    var lambda = Expression.Lambda<Func<TSource, bool>>(body, param);
    return source.Where(lambda);
}
Marc Gravell
The above methods also gives the errror:Method 'Boolean Like(System.String, System.String)' cannot be used on the client; it is only for translation to SQL.
Prasad
Ah... you didn't make your source clear. There is no really convenient "LIKE" equivalent unless you reference the VB libs, and that would work against db providers - does the Contains/StartsWith/EndsWith not help any?
Marc Gravell
Contains/Starts/Endwith also giving errors. You gave a real good solution for Orderby extension which i was searching for a long time. The Beauty of that code is it handles nested properties also. i am trying for similar one for 'Like'...
Prasad