IQueryable Extension: create lambda expression for querying a column for a keyword
I started with the IQueryable extension methods from this example on CodePlex.
What i believe i need is an IQueryable extension method to "Where", where the method signature looks like:
public static IQueryable<T> Where<T>(this IQueryable<T> source, string columnName, string keyword)
and effectively does this (assuming T.columnName is of type string):
source.Where(p => p.ColumnName.Contains("keyword"))
using the above CodePlex example, i think i understand how he got the OrderBy method working, but my problem seems a bit more complex and I don't know how to get the Contains("keyword") part working.
Thanks in advance,
--Ed
Update: 9/13/2010 6:26pm PST
I thought the following would work, but end up getting a NotSupportedException (The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.) when I execute the expression via Count(). Any ideas?
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;
}
}
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);
}
Well a couple of years later, but if anybody still need this, here it is:
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);
}
I called my method "Has" just to keep it short, sample usage:
filtered = filtered.AsQueryable().Has("City", strCity)
And you can concatenate to make it even more expressive:
filtered = filtered.AsQueryable().Has("Name", strName).Has("City", strCity).Has("State", strState);
By the way, the "IsNull()" attached to the strings is just another simple extension method:
public static Boolean IsNull(this string str)
{
return string.IsNullOrEmpty(str);
}
The .Contains("keyword")
part is exactly right in your example.
It's the p.ColumnName
part that's going to cause trouble.
Now, there are a number of ways of doing this, generally involving either reflection or Expression<>
, neither of which is particularly efficent.
The problem here is by passing the column name as a string, you are doing to undo that exact thing LINQ was invented to allow.
However, there are probably better ways of accomplishing your overall task besides that way.
So, let's look at alternate ways:
You want to be able to say :
var selector = new Selector("Column1", "keyword");
mylist.Where(item => selector(item));
and have it was the equivalent of
mylist.Where(item=> item.Column1.Contains("keyword"));
How 'bout we go with:
Func<MyClass, string> selector = i => i.Column1;
mylist.Where(item => selector(item).Contains("keyword"));
or
Func<MyClass, bool> selector = i => i.Column1.Contains("keyword");
mylist.Where(item => selector(item));
These are easily expanded for alternatives:
Func<MyClass, string> selector;
if (option == 1)
selector = i => i.Column1;
else
selector = i => i.Column2;
mylist.Where(item => selector(item).Contains("keyword"));
See, in generics type of the object works dynamically. So when p.ColumnName is taken as string, the Contains of string is been executed.
Generally for any lambda expression you specify, it parses the thing into an Expression and ultimately produces the output at runtime.
If you see my post : http://www.abhisheksur.com/2010/09/use-of-expression-trees-in-lamda-c.html you will understand how it works actually.
精彩评论