Extend the Where method for IQueryable [duplicate] - c#

This question already has an answer here:
extend Where for IQueryable
(1 answer)
Closed 8 years ago.
I want to extend the Where to something like this: .WhereEqual("Field", "SomeString") wich must be equivalent to .Where(p => p.Field== "SomeString"), here is my code:
public static IQueryable<T> WhereEqual<T>(this IQueryable<T> q, string Field, string Value)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, Field);
var val = Expression.Constant(Value);
var body = Expression.Equal(prop, val);
var exp = Expression.Lambda<Func<T, bool>>(body, param);
Type[] types = new Type[] { q.ElementType, typeof(bool) };
var mce = Expression.Call(
typeof(Queryable),
"Where",
types,
exp
);
return q.Provider.CreateQuery<T>(mce);
}
thanks to the debugger i can see that exp is set to {p => (p.Field== "SomeString")}, but the Call method throws the following exception :
"An exception of type 'System.InvalidOperationException' occurred in
System.Core.dll but was not handled in user code, Additional
information: No generic method 'Where' on type 'System.Linq.Queryable'
is compatible with the supplied type arguments and arguments. No type
arguments should be provided if the method is non-generic."
How can I give the Where the needed arguments ?
Thanks.

public static IQueryable<T> WhereEqual<T>(this IQueryable<T> q, string Field, string Value)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, Field);
var val = Expression.Constant(Value);
var body = Expression.Equal(prop, val);
var exp = Expression.Lambda<Func<T, bool>>(body, param);
return System.Linq.Queryable.Where(q, exp);
}

Related

LINQ to Entities OrderBy Expression Tree

I am trying to write a LINQ query to orderBy a dynamic property given by a string value.
Here is what my original code was:
Expression<Func<T, dynamic>> orderBy = i => i.GetType().GetProperty("PropertyName").GetValue(null);
When I tried to run this orderBy I got the following exception:
LINQ to Entities does not recognize the method
'System.Object GetValue(System.Object)' method, and this method cannot
be translated into a store expression.
I am trying to work around this by creating an expression tree that will give me the same result. The code should be able to return any type depending on the parameter but I'm having trouble with the return type. If I don't convert the value I get a different error saying w Nullable DateTime can't be converted to Object. Here is the code that I have so far:
ParameterExpression pe = Expression.Parameter(typeof(T), "s");
Expression<Func<T, dynamic>> orderByExpression = Expression.Lambda<Func<T, dynamic>>(Expression.Convert(Expression.Property(pe, "PropertyName"), typeof(object)), pe);
and my new exception:
Unable to cast the type
'System.Nullable`1[[System.DateTime]]' to type
'System.Object'. LINQ to Entities only supports casting EDM primitive
or enumeration types.
How would I write this expression tree to do return a dynamic type? Also is there a better way I should be doing this in LINQ?
There are no template arguments for Expression<Func<T, TT>> which will allow you to represent both reference types and value types. Of course, you can build the expressions, but you must interact with them via reflection.
This will properly sort a collection:
IOrderedEnumerable<TEntityType> SortMeDynamically<TEntityType>(IEnumerable<TEntityType> query, string propertyname)
{
var param = Expression.Parameter(typeof(TEntityType), "s");
var prop = Expression.PropertyOrField(param, propertyname);
var sortLambda = Expression.Lambda(prop, param);
Expression<Func<IOrderedEnumerable<TEntityType>>> sortMethod = (() => query.OrderBy<TEntityType, object>(k => null));
var methodCallExpression = (sortMethod.Body as MethodCallExpression);
if (methodCallExpression == null)
throw new Exception("Oops");
var method = methodCallExpression.Method.GetGenericMethodDefinition();
var genericSortMethod = method.MakeGenericMethod(typeof(TEntityType), prop.Type);
var orderedQuery = (IOrderedEnumerable<TEntityType>)genericSortMethod.Invoke(query, new object[] { query, sortLambda.Compile() });
return orderedQuery;
}
Or if you want it on an IQueryable (if you're using EF, for example)
IOrderedQueryable<TEntityType> SortMeDynamically<TEntityType>(IQueryable<TEntityType> query, string propertyname)
{
var param = Expression.Parameter(typeof(TEntityType), "s");
var prop = Expression.PropertyOrField(param, propertyname);
var sortLambda = Expression.Lambda(prop, param);
Expression<Func<IOrderedQueryable<TEntityType>>> sortMethod = (() => query.OrderBy<TEntityType, object>(k => null));
var methodCallExpression = (sortMethod.Body as MethodCallExpression);
if (methodCallExpression == null)
throw new Exception("Oops");
var method = methodCallExpression.Method.GetGenericMethodDefinition();
var genericSortMethod = method.MakeGenericMethod(typeof(TEntityType), prop.Type);
var orderedQuery = (IOrderedQueryable<TEntityType>)genericSortMethod.Invoke(query, new object[] { query, sortLambda });
return orderedQuery;
}

How to Type Cast dynamically to string while using Contains in Dynamic LINQ?

I want to use Dynamic LINQ Query to Search with some text in all Properties in a class. i am using following function to create expression. I am passing property name and search text to the method.
In that method if the property type is String then it is working fine. if the property type is int, DateTime, GUID. then it is not working.
As we know Contains method only for array of elements or for string. I am thinking the value to property should type cast to string. So How to do it? Solution with Explanation is help full.
i Collected code from this.
public static Expression<Func<T, bool>> ContainsExp<T>(string propertyName, string contains)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(contains, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
Well, you probably know that's it's not possible to use ToString() in linq to entities.
So the following question is : how can I convert other types to string.
For numeric values, you have SqlFunctions.StringConvert, but it has only overloads for double? and decimal?
For DateTime, you may find something using SqlFunctions.StringConvert after having applied SqlFunctions.DatePart on your DateTime (which probably means at least 3 call to SqlFunctions.DatePart, for year, month, day)
For Guid, I don't think there's a way to do it directly. One way (at db level, if you use Sql Server) could be to have a Computed column. The computed column could store a varchar converted representation of your GUID. Maybe there's a better way.
Anyway, here's at least a sample which should work for integer as well as string:
public static Expression<Func<T, bool>> ContainsExp<T>(string propertyName, string contains)
{
//first, get the type of your property
var propertyType = typeof(T).GetProperty(propertyName).PropertyType;
//no change
var parameterExp = Expression.Parameter(typeof (T), "type");
Expression propertyExp = Expression.Property(parameterExp, propertyName);
//if property's type is int
if (propertyType == typeof (int))
{
//convert your Expression to a nullable double (or nullable decimal),
//so that you can use SqlFunctions.StringConvert
propertyExp = Expression.Convert(propertyExp, typeof (double?));
//get the SqlFunctions.StringConvert method for nullable double
var stringConvertMethod = typeof (SqlFunctions).GetMethod("StringConvert", new[] {typeof (double?)});
//call StringConvert on your converted expression
propertyExp = Expression.Call(stringConvertMethod , propertyExp);
}
//no change
var method = typeof (string).GetMethod("Contains", new[] {typeof (string)});
var someValue = Expression.Constant(contains, typeof (string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
public static IQueryable<T> FieldsContains<T>(this IQueryable<T> query, List<string> fileds, string searchValue)
{
Expression predicate = null;
var parameterExpression = Expression.Parameter(typeof(T), "type");
foreach (string field in fileds)
{
var next = GetFieldContainsExpression<T>(parameterExpression, field, searchValue);
if (predicate == null)
{
predicate = next;
}
else
{
predicate = Expression.Or(predicate, next);
}
}
var lambda = Expression.Lambda<Func<T, bool>>(predicate, parameterExpression);
return query.Where(lambda);
}
private static Expression GetFieldContainsExpression<T>(ParameterExpression parameterExpression, string field, string value)
{
var propertyType = typeof(T).GetProperty(field).PropertyType;
Expression propertyExpression = Expression.Property(parameterExpression, field);
var filterValue = Expression.Constant(value);
var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
//call toString first to ignore type errors(datetime, int ...)
var toStringExpression = Expression.Call(propertyExpression, "ToString", Type.EmptyTypes);
var containsExpression = Expression.Call(toStringExpression, method, filterValue);
return containsExpression;
}

Building Expression Tree for string.Contains [duplicate]

This question already has answers here:
How do I create an expression tree to represent 'String.Contains("term")' in C#?
(4 answers)
Closed 9 years ago.
I'm struggling to build an expression tree so I can dynamically do filtering on some data.
I have come up with this, but it fails at the var lambda = line
foreach (var rule in request.Where.Rules)
{
var parameterExpression = Expression.Parameter(typeof(string), rule.Field);
var left = Expression.Call(parameterExpression, typeof(string).GetMethod("ToLower", Type.EmptyTypes));
var right = Expression.Constant(rule.Data.ToLower());
var method = typeof(string).GetMethod("Contains", new [] { typeof(string) });
var call = Expression.Call(left, method, right);
var lambda = Expression.Lambda<Func<T, bool>>(call, parameterExpression);
query = query.Where(lambda);
}
The var rule has a Field (ex "Name") which I want to compare with the text in rule.Data (ex 'tom'). So if T.Name.Contains("tom"); I want the query to include the record, otherwise, not.
The var query is of type IQueryable<T>
EDIT: Finally got it working with this code:
foreach (var rule in request.Where.Rules)
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, rule.Field);
var value = Expression.Constant(rule.Data);
var type = value.Type;
var containsmethod = type.GetMethod("Contains", new[] { typeof(string) });
var call = Expression.Call(property, containsmethod, value);
var lambda = Expression.Lambda<Func<T, bool>>(call, parameter);
query = query.Where(lambda);
}
You are almost there, but your parameter expression should be of type T, not String, you are also missing the expression that is getting the property from type T like name.
What you should roughly have is this
val -> Expression.Constant(typeof(string), rule.Field)
parameter -> Expression.Parameter(typeof(T), "p")
property -> Expression.Property(parameter, "PropertyName")
contains -> Expression.Call(property, containsmethod, val)
equals true -> Expression.True or equals, something like that
I am freehanding all of that, so it's likely somewhat different to be valid. The resulting expression should be something like this
p => p.Name.Contains(val)
If you want to create Where query, you must create lambda then call Where on query and pass lambda.
Try this:
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, parameter);
MethodCallExpression expression = Expression.Call(typeof(Queryable), "Where",
new[] { typeof(T) }, query.Expression, lambda);
query = query.Provider.CreateQuery<T>(expression);
instead of
var result = Expression.IsTrue(call);
query = query.Provider.CreateQuery<T>(result);

How to add an AND/OR expression to the following dynamic linq expression

T is a type that may or may not have a specific property, lets say 'City'. For the types that have a property named 'City', I would like to restrict the records such that only residents of the Gotham are returned and they are sorted.
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values)
{
var type = typeof(T);
var property = type.GetProperty(ordering);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { type, property.PropertyType },
source.Expression,
Expression.Quote(orderByExp));
string propertyToRestrictOn = "City";
string restrictedValue = "Gotham";
var restrictedProperty = type.GetProperty(propertyToRestrictOn);
if(null ! = restrictedProperty )
{
// TODO: What to add here so than only those records are returned that have a
// property named City and the value is 'Gotham'???
}
return source.Provider.CreateQuery<T>(resultExp);
}
if possible please name/link some helpful literature here as well just in case I have to create more complex queries i.e. mix And/OR
The code was borrowed from
How do I apply OrderBy on an IQueryable using a string column name within a generic extension method?
I'm not quite sure if I have understood you correctly, but I think I was in the same situation a few months ago.
The posted code here was my solution.
I think you should be especially interested in line 24.
Edit:
PropertyInfo p = ... // I used reflection in my examply to get properties with a certain Attribute
var expressionParameter = Expression.Parameter(typeof(SomeClass), "lambda");
var parameter = new [] { expressionParameter };
var propertyAccess = Expression.Property(expressionParameter, p);
var nullCheck = Expression.NotEqual(propertyAccess, Expression.Constant(null, p.PropertyType));
var nullCheckLambda = Expression.Lambda<Func<SomeClass, Boolean>>(nullCheck, parameter);
var containsMethodInfo = typeof(String).GetMethod("Contains", new[] { typeof(String) });
var contains = Expression.Call(propertyAccess, containsMethodInfo, Expression.Constant("ell"));
var containsLambda = Expression.Lambda<Func<SomeClass, Boolean>>(contains, new[] { expressionParameter });
var predicate = Expression.Lambda<Func<SomeClass, Boolean>>(
// line 24
Expression.AndAlso(nullCheckLambda.Body, containsLambda.Body), parameter);
Console.WriteLine(predicate.ToString());
I think you're making this harder than you have to. In the first part of your code (the OrderBy()), you don't actually need to generate the whole query expression, just the lambda inside it.
And in the second part (the optional Where()) you can do pretty much the same thing, just add Expression.Equal() and Expression.Constant():
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering)
{
var type = typeof(T);
var property = type.GetProperty(ordering);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
// necessary for value types to work
var cast = Expression.Convert(propertyAccess, typeof(object));
var orderByExp = Expression.Lambda<Func<T, object>>(cast, parameter);
IQueryable<T> result = source.OrderBy(orderByExp);
string propertyToRestrictOn = "City";
string restrictedValue = "Gotham";
var restrictedProperty = type.GetProperty(propertyToRestrictOn);
if (restrictedProperty != null)
{
var restrictionParameter = Expression.Parameter(type, "p");
var restrictionPropertyAccess =
Expression.MakeMemberAccess(restrictionParameter, restrictedProperty);
var restrictionEquality =
Expression.Equal(restrictionPropertyAccess,
Expression.Constant(restrictedValue));
var whereExp =
Expression.Lambda<Func<T, bool>>(restrictionEquality, restrictionParameter);
result = result.Where(whereExp);
}
return result;
}
Also, if your method is going to do more than just ordering, I think you shouldn't call it OrderBy().
You're half-way there already. You have the ordered expression already, so you're just going to use the Queryable.OrderBy expression call in a Queryable.Where expression (or vice-versa, doesn't really matter).
if(null != restrictedProperty )
{
var notEqualExp = Expression.NotEqual(parameter,
Expression.Constant(restrictedValue, typeof(string)));
resultExp = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { type },
resultExp,
Expression.Lambda(notEqualExp, parameter));
}
Haven't worked with building Expressions by hand in a while, so this is purely being done from memory. However, it should at least get you started and give you something to work with.
P.S. I would actually perform this check BEFORE the OrderBy method call. That way you end up with Queryably.Where(...).OrderBy(...) instead. But I guess if this is getting translated by the provider anyway, then shouldn't matter. However, just something I'd do to reduce any ambiguity of the generated query.

Search through Where clause in LINQ, using dynamic properties

I'm basically trying to construct a query, and I don't know why microsoft made this so difficult in Entity Framework and LINQ. I have various parameter STRINGS. So if you see a variable, assume it's a string passed in from somewhere.
users = this.entities.tableUsers
.Where(searchfield+" LIKE %#0%", search)
.OrderBy(x => x.GetType().GetProperty(order_by).GetValue(x, null).ToString())
.Skip(Convert.ToInt32(limit_begin))
.Take(Convert.ToInt32(limit_end))
.ToList();
My question is what to put inside "Where()" function in LINQ.
I want to search a field with string "searchfield", for the value .contains() "search".
Not sure why Visual Studio won't let me do this easily.
I've tried this as well, no luck:
.Where(x => x.GetType().GetProperty(searchfield).GetValue(x, null).ToList().Contains(search))
Note: I don't want to install any new libraries, this should be incredibly easy and simple for a modern language. I don't mind if the query returns all the rows and I search through it AFTER with .Contains().
This is not trivial, but I believe it can be done. The following has not been tested. The code is borrowed from here.
Create a helper method somewhere like
public static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string containsValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
public static Expression<Func<T, TKey>> GetPropertyExpression<T, TKey>(string propertyName)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var exp = Expression.Property(parameterExp, propertyName);
return Expression.Lambda<Func<T, TKey>>(exp, parameterExp);
}
Use it like
users = this.entities.tableUsers
.Where(GetContainsExpression<User>(searchfield, search))
.OrderBy(GetPropertyExpression<User, string>(searchfield))
...
UPDATE
As an alternative, you could create extension methods to provide a cleaner syntax. Create the following methods in a static class somewhere:
public static IQueryable<T> WhereStringContains<T>(this IQueryable<T> query, string propertyName, string contains)
{
var parameter = Expression.Parameter(typeof(T), "type");
var propertyExpression = Expression.Property(parameter, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(contains, typeof(string));
var containsExpression = Expression.Call(propertyExpression, method, someValue);
return query.Where(Expression.Lambda<Func<T, bool>>(containsExpression, parameter));
}
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName)
{
var propertyType = typeof(T).GetProperty(propertyName).PropertyType;
var parameter = Expression.Parameter(typeof(T), "type");
var propertyExpression = Expression.Property(parameter, propertyName);
var lambda = Expression.Lambda(propertyExpression, new[] { parameter });
return typeof(Queryable).GetMethods()
.Where(m => m.Name == "OrderBy" && m.GetParameters().Length == 2)
.Single()
.MakeGenericMethod(new[] { typeof(T), propertyType })
.Invoke(null, new object[] { query, lambda }) as IOrderedQueryable<T>;
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName)
{
var propertyType = typeof(T).GetProperty(propertyName).PropertyType;
var parameter = Expression.Parameter(typeof(T), "type");
var propertyExpression = Expression.Property(parameter, propertyName);
var lambda = Expression.Lambda(propertyExpression, new[] { parameter });
return typeof(Queryable).GetMethods()
.Where(m => m.Name == "OrderByDescending" && m.GetParameters().Length == 2)
.Single()
.MakeGenericMethod(new[] { typeof(T), propertyType })
.Invoke(null, new object[] { query, lambda }) as IOrderedQueryable<T>;
}
Then you can call them like:
var users = this.entities.tableUsers.WhereStringContains(searchField, search)
.OrderBy(searchField);
this should be incredibly easy and simple for a modern language
No, it should not if it goes against that language paradigm. LINQ and Entity Framework (as well as any other decent ORM out there) are made precisely to avoid what you're trying to accomplish: non-typed and non-compiler-verifiable queries. So basically you're forcing square peg into round hole.
You can still take a look at Dynamic LINQ.
You'll have to build an expression tree to pass to the Where method. Here's a loose adaptation of some code I have lying about:
string searchfield, value; // Your inputs
var param = Expression.Parameter(typeof(User), "user");
return Expression.Lambda<Func<T, bool>>(
Expression.Call(
Expression.Property(
param,
typeof(User).GetProperty(searchfield)),
typeof(string).GetMethod("Contains"),
Expression.Constant(value)),
param);
That will generate an appropriate expression to use as the parameter to Where.
EDIT: FYI, the resultant expression will look something like user => user.Foo.Contains(bar).
EDIT: To sort, something like this (ripped from my DynamicOrderList class):
private IQueryable<T> OrderQuery<T>(IQueryable<T> query, OrderParameter orderBy)
{
string orderMethodName = orderBy.Direction == SortDirection.Ascending ? "OrderBy" : "OrderByDescending";
Type t = typeof(T);
var param = Expression.Parameter(t, "user");
var property = t.GetProperty(orderBy.Attribute);
return query.Provider.CreateQuery<T>(
Expression.Call(
typeof(Queryable),
orderMethodName,
new Type[] { t, typeof(string) },
query.Expression,
Expression.Quote(
Expression.Lambda(
Expression.Property(param, property),
param))
));
}
My answer to your other question about this:
When creating dynamic linq sorting and searching order statements in Entity Framework

Categories