【问题标题】:IQueryable Extension: create lambda expression for querying a column for a keywordIQueryable 扩展:创建用于查询关键字列的 lambda 表达式
【发布时间】:2010-09-13 18:45:53
【问题描述】:

我从 CodePlex 上的这个示例中的 IQueryable 扩展方法开始。

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

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

并且有效地做到了这一点(假设 T.columnName 是字符串类型):

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

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

提前致谢,

--埃德

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

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

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

【问题讨论】:

  • 你解决过“LINQ to Entities 不支持”的问题吗?

标签: c# lambda extension-methods


【解决方案1】:
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);
}

【讨论】:

  • 这不起作用,因为 Queryable.Contains 期望类型 T 作为参数,而不是字符串。这就是为什么我需要做 Queryable.Where(p => p.EdsColumn.Contains("searchthis")
  • @Ed.S.:将 T 更改为 string。这样更好吗?
  • 我认为需要有两个机构。一个用于 String.Contains,另一个用于 Where。 var stringBody = Expression.Call( typeof(string), "Contains", new Type[] { typeof(string) }, Expression.Property(arg, columnName), Expression.Constant(keyword)); var whereBody = Expression.Call( typeof(Queryable), "Where", new Type[] { typeof(Queryable) }, -- 这就是我迷路的地方。
  • @Ed.S.:您的whereBody 正是我的代码中的return source.Where(predicate); 返回的内容。它首先构建谓词p =&gt; p.ColumnName.Contains("keyword"),然后返回source.Where(predicate)
  • @dtb:仍然收到“'System.String' 类型上不存在方法 'Contains'。”有任何想法吗?在正文中,我将两个“typeof”都更改为字符串,因为 Queryable.Contains(this IQueryable,...) 不起作用
【解决方案2】:

几年后,但如果有人仍然需要这个,这里是:

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

【讨论】:

    【解决方案3】:

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

    p.ColumnName 部分会造成麻烦。

    现在,有很多方法可以做到这一点,通常涉及反射或Expression&lt;&gt;,这两种方法都不是特别有效。

    这里的问题是通过将列名作为字符串传递,您正在撤消 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"));
    

    【讨论】:

    • 谢谢,我已将您的建议纳入我的“正在进行的工作”中。
    【解决方案4】:

    看,对象的泛型类型是动态工作的。所以当 p.ColumnName 作为字符串时,字符串的 Contains 被执行。

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

    如果你看到我的帖子: http://www.abhisheksur.com/2010/09/use-of-expression-trees-in-lamda-c.html 您将了解它实际上是如何工作的。

    【讨论】:

      猜你喜欢
      • 2015-02-12
      • 2012-11-29
      • 1970-01-01
      • 1970-01-01
      • 2019-04-08
      • 2015-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多