【问题标题】:Extending IQueryable to return objects where a property contains a string扩展 IQueryable 以返回属性包含字符串的对象
【发布时间】:2009-11-06 14:39:38
【问题描述】:

我看到很多类似下面的代码

var customrs = MyDataContext.Customers.Where(...);
if (!String.IsNullOrEmpty(input)) {
  customers = customers.Where(c => c.Email.Contains(input));
}

我想把它放在一个扩展方法中,在调用 IQueryable 上的 Where 之前检查输入是否有效,以便可以像这样调用它

customers = MyDataContext.Customers.Where(...)
  .ContainsText(c => c.Email, input);

我的扩展方法是这样的

public static IQueryable<T> ContainsText<T>(this IQueryable<T> source, Expression<Func<T, string>> selector, string text) {
  if (String.IsNullOrEmpty(text) {
    return source;
  }
  else {
    //do something here
  }
}

如何在解析的表达式上调用 Contains()?或者是否有另一种方法可以返回 IQueryable 的结果,其中已解析的表达式包含已解析的文本?

更新:用于 Linq to Sql

【问题讨论】:

    标签: c# linq-to-sql c#-3.0 iqueryable


    【解决方案1】:

    tvanfosson 有正确的想法来构建导致this answerthis question 的表达式。因此,为了完整起见,这里是一个完整的工作解决方案

    表达式生成器

    public static class ExpressionBuilder {
    
      public static Expression<Func<T, bool>> ContainsText<T>(string propertyName, string text) {
        var paramExp = Expression.Parameter(typeof(T), "type");
        var propExp = Expression.Property(paramExp, propertyName);
        var methodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) });
        var valueExp = Expression.Constant(text, typeof(string));
        var methCall = Expression.Call(propExp, methodInfo, valueExp);
        return Expression.Lambda<Func<T, bool>>(methCall, paramExp);
      }
    
    }
    

    扩展方法

    public static class IQueryableExtensions {
    
      public static IQueryable<T> ContainsText<T>(this IQueryable<T> source, Expression<Func<T, string>> selector, string text) {
        if (source == null) {
          throw new ArgumentNullException();
        }
        if (text.IsNullOrEmpty()) {
          return source;
        }
        string propName = ((MemberExpression)selector.Body).Member.Name;
        return source.Where(ExpressionBuilder.ContainsText<T>(propName, text));
      }
    
    }
    

    像这样调用

    var customers = MyDataContext.Customers.Where(/* some code */)
      .ContainsText(c => c.Email, input);
    

    【讨论】:

    • 嘿,我有一个关于这段代码的问题。它看起来像是我可以使用的东西,但有一件事:当我链接它们时,表达式被转换为带有 AND 语句的 SQL 查询:... WHERE thing LIKE ... AND thing2 LIKE ... 你知道一些吗将这种行为更改为 OR 的方法?这是在某些环境中完成的吗?还是我应该写很多以某种方式使用 Expression.Or 的代码?
    【解决方案2】:

    我想我会在 String 上进行扩展,而不是在 IQueryable 上。

    public static bool ContainsIfNotEmpty( this string source, string text )
    {
        if (string.IsNullOrEmpty(text))
        {
           return true; // select all elements
        }
        if (string.IsNullOrEmpty(source))
        {
           return false; // select no elements
        }
        return source.Contains(text); // select only matching
    }
    

    然后将其用作:

    customers = MyDataContext.Customers
                             .Where( c => c.Email.ContainsIfNotEmpty( input ) );
    

    请注意,这需要 LINQ to 对象。如果您需要将它与 LINQ to SQL 一起使用,那么我建议使用构建器方法构建表达式。请注意,以下内容未经测试,因为我现在无法访问 VS。您可能想查看 Andrew Peters 的 blog entry 以获取类似示例和/或 Expression 类的文档。

    public static class ExpressionBuilders
    {
        public static Expression<Func<T,bool>> ContainsBuilder<T>( string column, string text )
        {
              ParameterExpression parameter = new Expression.Parameter( typeof(T), "t" );
    
              if (string.IsNullOrEmpty(text))
              {
                  return (Expression<Func<T,bool>>)QueryExpression.Lambda( Expression.Constant( true ), parameter );
              }
    
              MethodInfo contains = typeof(T).GetMethod("Contains");
              Expression textExpression = Expression.Constant(text);
              Expression containsExpression = Expression.Call(parameter,contains,textExpression);
    
              return (Expression(Func<T,bool>))QueryExpression.Lambda( containsExpression, parameter );
        }
    }
    

    用作:

    var predicate = ExpressionBuilders.ContainsBuilder<Customer>( "Email", input );
    
    customers = MyDataContext.Customers.Where( predicate );
    

    【讨论】:

    • 我正在寻找包含(输入)而不是等于(输入),这可能使用动态 Linq 吗?它适用于 Linq to Sql。
    • 抱歉——我错过了。它变得更加复杂,您不能使用动态 LINQ,而必须自己构建表达式。我已经包含了一些(未经测试的)代码,它们可能有效,或者至少给你一个关于如何完成的提示。
    • 谢谢,这让我找到了解决方案。您的表达式生成器中有几个语法错误,我没有代表编辑它们,所以我发布了一个完整的工作解决方案供任何人查看。
    【解决方案3】:

    使用IEnumerable&lt;T&gt; 可以实现以下操作:

    public static IEnumerable<T> ContainsText<T>(
        this IEnumerable<T> source, Func<T, string> selector, string text)
    {
      return source.Where(
          x => (!string.IsNullOrEmpty(selector(x)) &&
               (selector(x).Contains(text))));
    }
    

    为了留在IQueryable&lt;T&gt;,您唯一的选择要么是 tvanfosson 建议的 Dynamic Linq,要么是 User Defined Functions

    但说实话,你还不如留在Where

    【讨论】:

      【解决方案4】:

      我认为你可以这样做:

      customers = MyDataContext.Customers.Where(c =>  (string.IsNullOrEmpty(input) || c.Email.Contains(input)));
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-24
        • 1970-01-01
        • 2019-06-27
        • 1970-01-01
        • 2010-11-14
        相关资源
        最近更新 更多