views:

381

answers:

4

I see a lot of code similar to the following

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

I would like to put this in an extension method that checks the input is valid before invoking Where on IQueryable so that it can be called like

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

My extension method looks like this

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

How can I call Contains() on the expression parsed? Or is there another way to return IQueryable of results where the expression parsed contains the text parsed?

Update: It's for Linq to Sql

A: 

I think you can do this instead:

customers = MyDataContext.Customers.Where(c =>  (string.IsNullOrEmpty(input) || c.Email.Contains(input)));
Gregoire
+2  A: 

I think I would make the extension on String, not on 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
}

Then use it as:

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

Note that this requires LINQ to objects. If you need to use it with LINQ to SQL, then I'd suggest building the expression using a builder method. Note the following is untested as I don't have access to VS right now. You might want to look at Andrew Peters' blog entry for a similar example and/or the documentation on the Expression class.

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

Used as:

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

customers = MyDataContext.Customers.Where( predicate );
tvanfosson
I'm looking for Contains(input) not Equals(input), is that possible using dynamic Linq? It's for Linq to Sql.
David G
Sorry -- I missed that. It gets more complicated and you can't use Dynamic LINQ, but have to build the expression yourself. I've included some (untested) code that may work or at least give you a hint as to how it could be done.
tvanfosson
Thanks, this led me to a solution. There are a couple of syntax errors in your expression builder and I don't have the rep to edit them so I've posted a full working solution for anyone to see.
David G
A: 

The following would be possible using IEnumerable<T>:

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

In order to stay in the IQueryable<T> your only options would either be Dynamic Linq as tvanfosson has suggested or User Defined Functions.

But to be honest, you might aswell just stay with Where.

Yannick M.
+1  A: 

tvanfosson had the right idea with building the expression which led to this answer to this question. So for completeness here is a full working solution

The expression builder

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

}

The extension method

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

}

Invoked like

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