Extending IQueryable to return objects where a property contains a string
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
开发者_如何学Gopublic 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
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);
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 );
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
.
I think you can do this instead:
customers = MyDataContext.Customers.Where(c => (string.IsNullOrEmpty(input) || c.Email.Contains(input)));
精彩评论