IQueryable Extension:创建lambda表达式以查询关键字的列

时间:2010-09-13 18:45:53

标签: c# lambda extension-methods

我从CodePlex上的示例开始使用IQueryable扩展方法。

我认为我需要的是“Where”的IQueryable扩展方法,其中方法签名如下:

public static IQueryable<T> Where<T>(this IQueryable<T> source, string columnName, string keyword)

并且有效地执行此操作(假设T.columnName的类型为string):

source.Where(p => p.ColumnName.Contains("keyword"))

使用上面的CodePlex示例,我想我理解他如何使OrderBy方法工作,但我的问题似乎有点复杂,我不知道如何让Contains(“关键字”)部分工作。

提前致谢,

- 埃德

更新:9/13/2010太平洋标准时间下午6:26

我认为以下方法可行,但最终得到NotSupportedException(LINQ to Entities中不支持LINQ表达式节点类型'Invoke'。)当我通过Count()执行表达式时。有什么想法吗?

    public static IQueryable<T> Where<T>(this IQueryable<T> source, string columnName, string keyword)
    {
        var type = typeof(T);
        var property = type.GetProperty(columnName);
        if (property.PropertyType == typeof(string))
        {
            var parameter = Expression.Parameter(type, "p");
            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var sel = Expression.Lambda<Func<T, string>>(propertyAccess, parameter);
            var compiledSel = sel.Compile();
            return source.Where(item => compiledSel(item).Contains(keyword));
        }
        else
        {
            return source;
        }
    }

4 个答案:

答案 0 :(得分:9)

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);
}

答案 1 :(得分:6)

好几年后,但是如果还有人需要这个,那么它就是:

    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);
    }

我打电话给我的方法“Has”只是为了保持简短,样本用法:

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

你可以连接起来,使它更具表现力:

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

顺便说一句,附加到字符串的“IsNull()”只是另一种简单的扩展方法:

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

答案 2 :(得分:1)

.Contains("keyword")部分在您的示例中完全正确。

p.ColumnName部分会造成麻烦。

现在,有很多方法可以做到这一点,通常涉及反射或Expression<>,两者都没有特别高效。

这里的问题是通过将列名称作为字符串传递,您正在撤消LINQ发明允许的确切事物。

但是,除此之外,还有更好的方法可以完成整体任务。

所以,让我们看看其他方式:

您希望能够说:

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

并且它相当于

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

我们怎么样:

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

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

这些很容易扩展为替代品:

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

答案 3 :(得分:0)

请参阅,在泛型类型的对象动态工作。因此,当p.ColumnName被视为字符串时,已执行字符串的包含。

通常,对于您指定的任何lambda表达式,它会将事物解析为Expression,并最终在运行时生成输出。

如果你看到我的帖子: http://www.abhisheksur.com/2010/09/use-of-expression-trees-in-lamda-c.html 你会明白它是如何运作的。