IQueryable WhereLike extension with unknown property type

c# dynamic-linq

Question

I'm trying to create a WhereLike extension to IQueryable but I can't know the type of the property at runtime.

Here is my code:

    public static IQueryable WhereLike(this IQueryable source, string propertyName, string pattern)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (propertyName == null) throw new ArgumentNullException("propertyName");

        var a = Expression.Parameter(typeof(object), "a");
        var prop = Expression.Property(a, propertyName);

        return source.Provider.CreateQuery(
            Expression.Call(
                typeof(SqlMethods), "Like",
                null,
                prop, Expression.Constant(pattern)));
    }

I get the exception: Instance property 'foo' is not defined for type 'System.Object'

Do you know a way to handle property setting without knowing target type at compile time ?

1
1
7/7/2017 1:50:21 PM

Accepted Answer

If you are able to use the generic IQueryable<T> variant, this becomes a much easier problem since you no longer need CreateQuery and you can execute directly against the IQueryable<T> source.

public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, string propertyName, 
    string pattern)
{
    if (source == null) throw new ArgumentNullException("source");
    if (propertyName == null) throw new ArgumentNullException("propertyName");

    var a = Expression.Parameter(typeof(T), "a");
    var prop = Expression.PropertyOrField(a, propertyName);

    var expr = Expression.Call(
            typeof(SqlMethods), "Like",
            null,
            prop, Expression.Constant(pattern));

    var lambda = Expression.Lambda<Func<T, bool>>(expr, a); 
    return source.Where(lambda);
}

Note two key points:

Instead of only grabbing properties, if we use PropertyOrField we can properly support code generated for Linq-2-SQL that may be exposing fields.

In addition, since we are executing against the IQueryable<T> source, we need to create a lambda expression from the results of our "Like" MethodCallExpression.


If you need the non-generic variant, you can still accomplish the same thing, although you'll need to wrap your Like MethodCallExpression in a Where MethodCallExpression in order for it to be properly structured:

public static IQueryable WhereLike(this IQueryable source, string propertyName, 
    string pattern)
{
    if (source == null) throw new ArgumentNullException("source");
    if (propertyName == null) throw new ArgumentNullException("propertyName");

    var a = Expression.Parameter(source.GetType().GetGenericArguments().First(), "a");
    var prop = Expression.PropertyOrField(a, propertyName);

    var expr = Expression.Call(
            typeof(SqlMethods), "Like",
            null,
            prop, Expression.Constant(pattern));

    MethodCallExpression whereCallExpression = Expression.Call(
            typeof(Queryable),
            "Where",
            new Type[] { source.ElementType },
            source.Expression,
            Expression.Lambda(expr, a));

    return source.Provider.CreateQuery(whereCallExpression);
}

You can invoke either variant with wildcards:

var data = source.WhereLike("ColumnName", "%o%");   
1
7/7/2017 3:17:11 PM


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow