IQueryable Extension: create lambda expression for querying a column for a keyword

人走茶凉 提交于 2019-12-01 13:45:13
public static IQueryable<T> Where<T>(
    this IQueryable<T> source, string columnName, string keyword)
{
    var arg = Expression.Parameter(typeof(T), "p");

    var body = Expression.Call(
        Expression.Property(arg, columnName),
        "Contains",
        null,
        Expression.Constant(keyword));

    var predicate = Expression.Lambda<Func<T, bool>>(body, arg);

    return source.Where(predicate);
}

Well a couple of years later, but if anybody still need this, here it is:

    public static IQueryable<T> Has<T>(this IQueryable<T> source, string propertyName, string keyword)
    {
        if (source == null || propertyName.IsNull() || keyword.IsNull())
        {
            return source;
        }
        keyword = keyword.ToLower();

        var parameter = Expression.Parameter(source.ElementType, String.Empty);
        var property = Expression.Property(parameter, propertyName);

        var CONTAINS_METHOD = typeof(string).GetMethod("Contains", new[] { typeof(string) });
        var TO_LOWER_METHOD = typeof(string).GetMethod("ToLower", new Type[] { });

        var toLowerExpression = Expression.Call(property, TO_LOWER_METHOD);
        var termConstant = Expression.Constant(keyword, typeof(string));
        var containsExpression = Expression.Call(toLowerExpression, CONTAINS_METHOD, termConstant);

        var predicate = Expression.Lambda<Func<T, bool>>(containsExpression, parameter);

        var methodCallExpression = Expression.Call(typeof(Queryable), "Where",
                                    new Type[] { source.ElementType },
                                    source.Expression, Expression.Quote(predicate));

        return source.Provider.CreateQuery<T>(methodCallExpression);
    }

I called my method "Has" just to keep it short, sample usage:

filtered = filtered.AsQueryable().Has("City", strCity)

And you can concatenate to make it even more expressive:

filtered = filtered.AsQueryable().Has("Name", strName).Has("City", strCity).Has("State", strState);

By the way, the "IsNull()" attached to the strings is just another simple extension method:

    public static Boolean IsNull(this string str)
    {
        return string.IsNullOrEmpty(str);
    }

The .Contains("keyword") part is exactly right in your example.

It's the p.ColumnName part that's going to cause trouble.

Now, there are a number of ways of doing this, generally involving either reflection or Expression<>, neither of which is particularly efficent.

The problem here is by passing the column name as a string, you are doing to undo that exact thing LINQ was invented to allow.

However, there are probably better ways of accomplishing your overall task besides that way.

So, let's look at alternate ways:

You want to be able to say :

   var selector = new Selector("Column1", "keyword");
   mylist.Where(item => selector(item));

and have it was the equivalent of

    mylist.Where(item=> item.Column1.Contains("keyword"));

How 'bout we go with:

   Func<MyClass, string> selector = i => i.Column1;
   mylist.Where(item => selector(item).Contains("keyword"));

or

   Func<MyClass, bool> selector = i => i.Column1.Contains("keyword");
   mylist.Where(item => selector(item));

These are easily expanded for alternatives:

   Func<MyClass, string> selector;
   if (option == 1)
        selector = i => i.Column1;
   else
        selector = i => i.Column2;
   mylist.Where(item => selector(item).Contains("keyword"));

See, in generics type of the object works dynamically. So when p.ColumnName is taken as string, the Contains of string is been executed.

Generally for any lambda expression you specify, it parses the thing into an Expression and ultimately produces the output at runtime.

If you see my post : http://www.abhisheksur.com/2010/09/use-of-expression-trees-in-lamda-c.html you will understand how it works actually.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!