I'm trying to create a WhereLike extension to IQueryable but I can't know the type of the property at runtime.
Here is my code:
public static IQueryable WhereLike(this IQueryable source, string propertyName, string pattern)
{
if (source == null) throw new ArgumentNullException("source");
if (propertyName == null) throw new ArgumentNullException("propertyName");
var a = Expression.Parameter(typeof(object), "a");
var prop = Expression.Property(a, propertyName);
return source.Provider.CreateQuery(
Expression.Call(
typeof(SqlMethods), "Like",
null,
prop, Expression.Constant(pattern)));
}
I get the exception: Instance property 'foo' is not defined for type 'System.Object'
Do you know a way to handle property setting without knowing target type at compile time ?
If you are able to use the generic IQueryable<T>
variant, this becomes a much easier problem since you no longer need CreateQuery
and you can execute directly against the IQueryable<T>
source.
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, string propertyName,
string pattern)
{
if (source == null) throw new ArgumentNullException("source");
if (propertyName == null) throw new ArgumentNullException("propertyName");
var a = Expression.Parameter(typeof(T), "a");
var prop = Expression.PropertyOrField(a, propertyName);
var expr = Expression.Call(
typeof(SqlMethods), "Like",
null,
prop, Expression.Constant(pattern));
var lambda = Expression.Lambda<Func<T, bool>>(expr, a);
return source.Where(lambda);
}
Note two key points:
Instead of only grabbing properties, if we use PropertyOrField we can properly support code generated for Linq-2-SQL that may be exposing fields.
In addition, since we are executing against the IQueryable<T>
source, we need to create a lambda expression from the results of our "Like" MethodCallExpression
.
If you need the non-generic variant, you can still accomplish the same thing, although you'll need to wrap your Like MethodCallExpression
in a Where MethodCallExpression
in order for it to be properly structured:
public static IQueryable WhereLike(this IQueryable source, string propertyName,
string pattern)
{
if (source == null) throw new ArgumentNullException("source");
if (propertyName == null) throw new ArgumentNullException("propertyName");
var a = Expression.Parameter(source.GetType().GetGenericArguments().First(), "a");
var prop = Expression.PropertyOrField(a, propertyName);
var expr = Expression.Call(
typeof(SqlMethods), "Like",
null,
prop, Expression.Constant(pattern));
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda(expr, a));
return source.Provider.CreateQuery(whereCallExpression);
}
You can invoke either variant with wildcards:
var data = source.WhereLike("ColumnName", "%o%");