Compound expression with variable comparator? - c#

I'm writing a query framework, and trying to make it as generic as possible.
Let's say I have a query based on person, and I want the ability to filter on both the first and last names, and in both cases I want to be able to use filter conditions like StartsWith, 'EndsWith, Contains, Equals.
So now I have a method:
private Expression<Func<Person, bool>> FirstNameFilter(Comparator comparator, string compareValue) {
switch (comparator) {
case Comparator.Equal:
return p => p.FirstName == compareValue;
case Comparator.Contains:
return p => p.FirstName.Contains(compareValue);
case Comparator.StartsWith:
return p => p.FirstName.StartsWith(compareValue);
// etc.
}
}
Now, I also want to be able to build the same filter for LastName. Seems silly and wasteful to copy and paste the whole thing over again, just replacing p.FirstName with p.LastName. I also have a bunch of other string fields that I want to filter on, and I really don't want to have to rewrite this whole method for each one!
Is there some way to abstract this, maybe using LinqKit, so that I can come out with a more generic method with the following approximate signature:
Expression<Func<Person, bool>> GetFilter(Expression<Func<Person, string>> stringExpression, Comparator comparator, string compareValue) {}
such that I could, inside FirstNameFilter, invoke it like so:
return GetFilter(p => p.FirstName, comparator, compareValue);

Something like that (untested, but you have the idea) should help you to build the needed expressions :
public static class LinqQueries
{
private static MethodInfo toLowerMethod = typeof(String).GetMethod("ToLower", Type.EmptyTypes);
private static MethodInfo startsWithMethod= typeof(String).GetMethod("StartsWith", new Type[] { typeof(String) });
private static MethodInfo containsMethod = typeof(String).GetMethod("Contains", new Type[] { typeof(String) });
private static MethodInfo endsWithMethod= typeof(String).GetMethod("EndsWith", new Type[] { typeof(String) });
public static Expression<Func<T, bool>> GetFilter(Expression<Func<T, string>> expression, Comparator comparator, string compareValue) {
ParameterExpression parameterExpression = null;
var memberExpression = GetMemberExpression(expression.Body, out parameterExpression);
Expression constExp = Expression.Constant(compareValue);
switch (comparator) {
case Comparator.Contains:
memberExpression = Expression.Call(memberExpression, containsMethod,constExp);
break;
case Comparator.StartsWith:
memberExpression = Expression.Call(memberExpression, startsWithMethod, constExp);
break;
//etc.
default :
memberExpression = Expression.Equal(memberExpression, constExp);
break;
}
return Expression.Lambda<Func<T, bool>>(memberExpression, new[]{parameterExpression});
}
private static Expression GetMemberExpression(Expression expression, out ParameterExpression parameterExpression)
{
parameterExpression = null;
if (expression is MemberExpression)
{
var memberExpression = expression as MemberExpression;
while (!(memberExpression.Expression is ParameterExpression))
memberExpression = memberExpression.Expression as MemberExpression;
parameterExpression = memberExpression.Expression as ParameterExpression;
return expression as MemberExpression;
}
if (expression is MethodCallExpression)
{
var methodCallExpression = expression as MethodCallExpression;
parameterExpression = methodCallExpression.Object as ParameterExpression;
return methodCallExpression;
}
return null;
}
}

Related

Expression tree works when manual input, but not coming from another class

Night, fellow citizens!
I`ve been making a little program to build expressions, but it doesn't work coming from this class.
I've tested it with the object comparer, but it doesn't find any differences.
public class ExpressionUtility<T>
{
public ParameterExpression GetLambdaParam()
{
return Expression.Parameter(typeof(T), "p");
}
public MemberExpression GetProperty(string propertyName)
{
return Expression.Property(GetLambdaParam(), propertyName);
}
public ConstantExpression GetConstant(object value, Type type)
{
return Expression.Constant(value, type);
}
public BinaryExpression BuildBinaryExpression(ExpressionObject expressionObj)
{
if (expressionObj.ExpressionType == ExpressionType.Equal)
return Expression.Equal(GetProperty(expressionObj.PropertyName), GetConstant(expressionObj.Value, expressionObj.ObjectType));
if (expressionObj.ExpressionType == ExpressionType.GreaterThan)
return Expression.GreaterThan(GetProperty(expressionObj.PropertyName), GetConstant(expressionObj.Value, expressionObj.ObjectType));
if (expressionObj.ExpressionType == ExpressionType.LessThan)
return Expression.LessThan(GetProperty(expressionObj.PropertyName), GetConstant(expressionObj.Value, expressionObj.ObjectType));
if (expressionObj.ExpressionType == ExpressionType.GreaterThanOrEqual)
return Expression.GreaterThanOrEqual(GetProperty(expressionObj.PropertyName), GetConstant(expressionObj.Value, expressionObj.ObjectType));
if (expressionObj.ExpressionType == ExpressionType.LessThanOrEqual)
return Expression.LessThanOrEqual(GetProperty(expressionObj.PropertyName), GetConstant(expressionObj.Value, expressionObj.ObjectType));
else
throw new Exception("Binary Expression could not be formed!");
}
public Expression<Func<T, bool>> BuildExpression(BinaryExpression expression)
{
return Expression.Lambda<Func<T, bool>>(expression, GetLambdaParam());
}
}
but it works here.
var param = Expression.Parameter(typeof(Company), "p");
var finalExpression = Expression.Lambda<Func<Company, bool>>(Expression.Equal(Expression.Property(param, "TradeName"),
Expression.Constant("Matt's SoftwareHouse")), param);
You're not using the same ParameterExpression in the body as in the one you pass in when creating the Lambda. Compare this:
Expression.Lambda<Func<int, bool>>(
Expression.Equal(
Expression.Constant(1),
Expression.Parameter(typeof(int), "x")
),
Expression.Parameter(typeof(int), "x")
)
To this:
var parameter = Expression.Parameter(typeof(int), "x");
Expression.Lambda<Func<int, bool>>(
Expression.Equal(
Expression.Constant(1),
parameter
),
parameter
)
They look almost identical, but the first example is invalid. The parameter that you access within the lambda's body needs to be the same parameter that is passed in to the Expression.Lambda method.

C# Turning an IEnumerable function that uses Dynamic linq into an IQueryable function

I've got this static function that returns the filtered list of items based on one or more filter items. It works but its set up to return an IEnumerable. The thing about this is that I still want to add to the expressionTree before compiling the set of expressions and executing it as a whole..
This is my current filter code modified from Pashov.
namespace JobsLedger.API.ControllerServices.Shared.OrderAndFIlterHelpers {
public class ExpressionFilter {
public string PropertyName { get; set; }
public object Value { get; set; }
public Comparison Comparison { get; set; }
}
public enum Comparison {
Equal,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
NotEqual,
Contains, //for strings
StartsWith, //for strings
IndexOf, //for strings
EndsWith, //for strings
BoolTest, //for bools
}
public static class DynamicFilteringHelper {
public static IEnumerable<T> ConstructAndExpressionTree<T>(IQueryable<T> source, List<ExpressionFilter> filters) {
if (filters.Count == 0)
return null;
ParameterExpression param = Expression.Parameter(typeof(T), "t");
Expression exp = null;
if (filters.Count == 1) {
exp = ExpressionRetriever.GetExpression<T>(param, filters[0]);
}
else {
exp = ExpressionRetriever.GetExpression<T>(param, filters[0]);
for (int i = 1; i < filters.Count; i++) {
exp = Expression.And(exp, ExpressionRetriever.GetExpression<T>(param, filters[i]));
}
}
var test = source.Where(Expression.Lambda<Func<T, bool>>(exp, param).Compile());
return test;
}
public static class ExpressionRetriever {
private static MethodInfo containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string), typeof(StringComparison) });
private static MethodInfo startsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string), typeof(StringComparison) });
private static MethodInfo endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string), typeof(StringComparison) });
private static MethodInfo booleanEqualMethod = typeof(bool).GetMethod("Equals", new Type[] { typeof(bool) });
public static Expression GetExpression<T>(ParameterExpression param, ExpressionFilter filter) {
MemberExpression member = Expression.Property(param, filter.PropertyName);
ConstantExpression constant = Expression.Constant(filter.Value);
ConstantExpression comparisonType = Expression.Constant(StringComparison.OrdinalIgnoreCase);
switch (filter.Comparison) {
case Comparison.Equal:
return Expression.Equal(member, constant);
case Comparison.GreaterThan:
return Expression.GreaterThan(member, constant);
case Comparison.GreaterThanOrEqual:
return Expression.GreaterThanOrEqual(member, constant);
case Comparison.LessThan:
return Expression.LessThan(member, constant);
case Comparison.LessThanOrEqual:
return Expression.LessThanOrEqual(member, constant);
case Comparison.NotEqual:
return Expression.NotEqual(member, constant);
case Comparison.Contains:
return Expression.Call(member, containsMethod, constant, comparisonType);
case Comparison.StartsWith:
return Expression.Call(member, startsWithMethod, constant, comparisonType);
case Comparison.IndexOf:
return Expression.NotEqual(
Expression.Call(
member,
"IndexOf",
null,
Expression.Constant(filter.Value, typeof(string)),
Expression.Constant(StringComparison.InvariantCultureIgnoreCase, typeof(StringComparison))
),
Expression.Constant(-1, typeof(int))
);
case Comparison.EndsWith:
return Expression.Call(member, endsWithMethod, constant);
case Comparison.BoolTest:
return Expression.Call(member, booleanEqualMethod, constant);
default:
return null;
}
}
}
}
}
At the end of the DynamicFilteringHelper method it has the expresson:
var test = source.Where(Expression.Lambda<Func<T, bool>>(exp, param).Compile());
This compiles the expressions and applies it to the source using a "where".
I wanted to instead produce an expressionTree that the sort function could add to before compiling.. In this, because its returning an IEnumerable it's been compiled and I would like to compile it after filtering and sorting first.
So I started to put together a Call that combined all the filtering expressions and applied it against the source using "where".
var filteredExpressions = Expression.Lambda(exp, param);
MethodCallExpression call = Expression.Call(
typeof(Queryable),
"where",
new[] { typeof(T) },
source.Expression,
Expression.Quote(filteredExpressions)
);
return source.Provider.CreateQuery<T>(call);
This does not work and I get "Operation is not valid due to the current state of the object."
Is there a way to return the expressonTree so that it can be added onto before I compile and retrieve the results? Or am I OK to compile this and then use it for Sort function..
I get the impression its best to create the expression tree in its entirety before executing it so thats why I wanted to return an IQueryable rather than IEnumerable...
Extra info on the sort method..
Just for your information I next want to call this sort static sort function..
public static class DynamicOrderingHelper {
public static IOrderedQueryable<T> OrderByAndThenBy<T>(IQueryable<T> source, List<KeyValuePair<string, SortDirection>> sortItems) {
IOrderedQueryable<T> sortedSource = null;
for (int i = 0; i < sortItems.Count; i++) {
if (i == 0) {
sortedSource = sortItems[0].Value == SortDirection.asc ? OrderingHelper(source, sortItems[0].Key, false, false) : OrderingHelper(source, sortItems[0].Key, true, false);
}
else {
sortedSource = sortItems[i].Value == SortDirection.asc ? OrderingHelper(sortedSource, sortItems[i].Key, false, true) : OrderingHelper(sortedSource, sortItems[i].Key, true, true);
}
}
return sortedSource;
}
private static IOrderedQueryable<T> OrderingHelper<T>(IQueryable<T> source, string propertyName, bool descending, bool anotherLevel) {
ParameterExpression param = Expression.Parameter(typeof(T), string.Empty); // I don't care about some naming
MemberExpression property = Expression.PropertyOrField(param, propertyName);
LambdaExpression sort = Expression.Lambda(property, param);
MethodCallExpression call = Expression.Call(
typeof(Queryable),
(!anotherLevel ? "OrderBy" : "ThenBy") + (descending ? "Descending" : string.Empty),
new[] { typeof(T), property.Type},
source.Expression,
Expression.Quote(sort));
return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(call);
}
EDIT
As Ivan suggested I did this and the return type has come back with "Operation invalid due to the current state of the object". I thought that this meant that it needed to be compiled but it wont accept that on IQueryable and .ToList() doesnt work either..
by the way this happens on the first of the two lines below.. source has 21 records but comes back with that error:
var test = source.Where(Expression.Lambda<Func<T, bool>>(exp, param));
return test;

Construct expression tree with chained properties?

I have a method that accepts Expression<Func<T, string>>, for example x => x.Name, and a term, and returns x => x.Name.Contains(term):
Given the model;
class X
{
public Y Y {get; set;}
}
class Y
{
public string Z {get; set;}
}
It works well for GenerateForMember<Y>(y => y.Z, "foobar"), but currently don't work for GenerateForMember<X>(x => x.Y.Z, "foobar"). It gives the exception
'Z' is not a member of 'UserQuery+X'
How to I update my method to work with chained properties?
Method is as follows:
protected Expression<Func<T, bool>> GenerateForMember<T>(Expression<Func<T,string>> expression, string term)
{
var type = typeof(T);
var memberExpression = ((expression.Body.NodeType == ExpressionType.Convert)
? ((UnaryExpression)expression.Body).Operand
: expression.Body) as MemberExpression;
ParameterExpression parameter = Expression.Parameter(type, type.Name.ToLower());
MemberExpression member = Expression.PropertyOrField(parameter, memberExpression.Member.Name);
var propertyInfo = memberExpression.Member as PropertyInfo;
var memberType = propertyInfo == null
? ((FieldInfo) memberExpression.Member).FieldType
: propertyInfo.PropertyType;
ConstantExpression constant = Expression.Constant(term, typeof(string));
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(member, method, constant);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameter);
}
You are dissecting the original expression and later re-construct it again. This is not necessary. You can use expression.Body directly in order to create the method call. Like this, it should work with any lambda expression.
var type = typeof(T);
ParameterExpression parameter = Expression.Parameter(type, type.Name.ToLower());
ConstantExpression constant = Expression.Constant(term, typeof(string));
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(expression.Body, method, constant);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameter);
Try this:
protected Expression<Func<T, bool>> GenerateForMember<T>(Expression<Func<T, string>> expression, string term)
{
ConstantExpression constant = Expression.Constant(term, typeof(string));
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(expression.Body, method, constant);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, expression.Parameters[0]);
}

Expression Tree with chained string methods

I have created a predicate that looks like:
p.Name.Contains("Saw")
and I have the following code that works:
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue)
{
PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName);
// ListOfProducts.Where(p => p.Contains(propertyValue))
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo);
MethodInfo methodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)});
ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string));
// Predicate Body - p.Name.Contains("Saw")
Expression call = Expression.Call(memberExpression, methodInfo, constantExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe);
return lambda;
}
But I want to change the predicate to:
p.Name.ToLower().Contains("Saw")
and I'm coming up blank. I know that I have to add something somewhere where the MethodInfo is defined.
Does anyone have a suggestion?
Rather than constructing the entire expression manually just because one tiny little piece is dynamic, you can use a regular lambda to define all of the contents that are in fact static, and then just replace the little bit that isn't.
Specifically, the general strategy you can use is to have a lambda with a parameter representing your little dynamic bit, and then using it in a regular lambda, and then replace all instances of that parameter with your dynamically constructed expression:
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(
string propertyName, string propertyValue)
{
Expression<Func<string, bool>> e = s => s.ToLower().Contains(propertyValue);
var parameter = Expression.Parameter(typeof(T));
var property = Expression.PropertyOrField(parameter, propertyName);
var body = e.Body.Replace(e.Parameters[0], property);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
This not only simplifies your original code, but makes other changes to this static code as easy as editing any regular old C# code, rather than requiring all of the expression manipulation, and the complexity (and loss of static typing) that comes along with it.
This solution uses the following method to replace all instances of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Another approach, allowing things to be even more high level, is to write a Compose method that lets you compose expressions easily. Conceptually we'll have two lambdas, and we want to create a lambda that represents invoking one and passing its result to the other, and then it returning the result:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
This uses more or less the same strategy that we used above, but it generalizes it instead of special casing it to your specific expressions.
We then have one remaining helper method to make before putting the pieces together; creating a method that represents accessing a property as defined by the string name of the property:
public static Expression<Func<T, string>> MemberSelector<T>(string propertyName)
{
var param = Expression.Parameter(typeof(T));
var body = Expression.PropertyOrField(param, propertyName);
return Expression.Lambda<Func<T, string>>(body, param);
}
Using these two helper methods (that aren't dependent on any particular situation), we can now construct the lambda that we want without any custom built expression manipulation:
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(
string propertyName, string propertyValue)
{
return MemberSelector<T>(propertyName)
.Compose(prop => prop.ToLower().Contains(propertyValue));
}
You have to get the expression for the ToLowermethod, then use it in the Contains expression
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue)
{
PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName);
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo);
//ToLower expression
MethodInfo toLowerMethodInfo = typeof (string).GetMethod("ToLower", new Type[]{});
Expression toLowerCall = Expression.Call(memberExpression, toLowerMethodInfo);
MethodInfo containsMethodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)});
ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string));
// Pass ToLowerCall to
Expression call = Expression.Call(toLowerCall, containsMethodInfo, constantExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe);
return lambda;
}
With respect to Andre's answer, which is essentially what I came up after Servy's initial comment, I wanted to post what I came up with:
private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue)
{
PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName);
// ListOfProducts.Where(p => p.Contains(propertyValue))
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo);
// Thanks to Servy's suggestion
Expression toLowerExpression = Expression.Call(memberExpression, typeof(string).GetMethod("ToLower", Type.EmptyTypes));
MethodInfo methodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)});
ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string));
// Predicate Body - p.Name.Contains("Saw")
Expression call = Expression.Call(toLowerExpression, methodInfo, constantExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe);
return lambda;
}

Dynamic Lambda Expression call

I'm getting this exception when I run this code.
ParameterExpression of type System.Int64 cannot be used for delegate parameter of type System.Object
I know it's something to do with the Expression.Lambda<func<object,bool>> part of the code. Overall, I want to pass any type of ParameterExpression into this method and it will call the expression.
public static IQueryable<T> OrderData<T>(IQueryable<T> data)
{
try
{
Order order = Order.ASC;
var result = Enum.TryParse<Order>(_gridSettings.SortOrder, true, out order);
if (_gridSettings.IsSearch)
{
data = ExpressionSort(order, data, typeof(T).GetProperty(_gridSettings.SortColumn));
}
else
{
data = ExpressionSort(order, data, _defaultColumn);
}
}
catch (Exception ex)
{
log.WriteLog(MethodBase.GetCurrentMethod(), LogLevel.FATAL, ex);
}
return data;
}
private static IQueryable<T> ExpressionSort<T>(Order order, IQueryable<T> data, PropertyInfo property)
{
// Compose the expression tree that represents the parameter to the predicate.
ParameterExpression paramExpression = Expression.Parameter(property.PropertyType, property.Name);
IQueryable<T> queryableData = data.AsQueryable<T>();
switch (order)
{
case Order.ASC:
return ExecuteCall(paramExpression, paramExpression, queryableData, "OrderBy");
case Order.DESC:
return ExecuteCall(paramExpression, paramExpression, queryableData, "OrderByDescending");
}
return data;
}
private static IQueryable<T> ExecuteCall<T>(Expression expression, ParameterExpression paramExpression, IQueryable<T> queryableData, string linqMethod)
{
MethodCallExpression callExpression = Expression.Call(
typeof(Queryable),
linqMethod,
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<object, bool>>(expression, new ParameterExpression[] { paramExpression }));
// Create an executable query from the expression tree.
return queryableData.Provider.CreateQuery<T>(callExpression);
}
EDIT:
I did see this answer to a similar question
Expression of type 'System.Int32' cannot be used for return type 'System.Object'
I do not know how to apply it to my code though
EDIT 2:
The main issue is that thisExpression.Lambda<Func<object, bool>>(conversion, new ParameterExpression[] { paramExpression })); line is giving me an exception. paramExpression contains an Int64 but its expectinng an object. I dont know how to dynamically tell the Func from the information I already have or if that is possible.
GOAL:
I am trying to do something like this data.OrderBy(x=>x.DynamicProperty);
This is what you asked for, I think... I've tested it and it seems to work.
// Caching of the reflection
private static readonly MethodInfo orderByMethod = GetOrderByMethod("OrderBy");
private static readonly MethodInfo orderByDescendingMethod = GetOrderByMethod("OrderByDescending");
private static IOrderedQueryable<TSource> ExpressionSort<TSource>(Order order, IQueryable<TSource> source, PropertyInfo property)
{
// Compose the expression tree that represents the parameter to
// the predicate.
// The expression you would use is source => source.Property,
// The parameter of the lambda, source
ParameterExpression sourceExpression = Expression.Parameter(typeof(TSource), "source");
// Accessing the expression
MemberExpression propertyExpression = Expression.Property(sourceExpression, property);
// The full lambda expression. We don't need the
// Expression.Lambda<>, but still the keySelector will be an
// Expression<Func<,>>, because Expression.Lambda does it
// authomatically. LambdaExpression is simply a superclass of
// all the Expression<Delegate>
LambdaExpression keySelector = Expression.Lambda(propertyExpression, sourceExpression);
// The OrderBy method we will be using, that we have cached
// in some static fields
MethodInfo method = order == Order.ASC ? orderByMethod : orderByDescendingMethod;
// Adapted from Queryable.OrderBy (retrieved from the reference
// source code), simply changed the way the OrderBy method is
// retrieved to "method"
return (IOrderedQueryable<TSource>)source.Provider.CreateQuery<TSource>(Expression.Call(null, method.MakeGenericMethod(new Type[]
{
typeof(TSource),
property.PropertyType
}), new Expression[]
{
source.Expression,
Expression.Quote(keySelector)
}));
}
private static MethodInfo GetOrderByMethod(string methodName)
{
// Here I'm taking the long and more correct way to find OrderBy/
// OrderByDescending: looking for a public static method with the
// right name, with two generic arguments and that has the
// parameters related to those two generic arguments in a certain
// way (they must be IQueryable<arg0> and Expression<Func<arg0,
// arg1>>
MethodInfo orderByMethod = (from x in typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
where x.Name == methodName
let generics = x.GetGenericArguments()
where generics.Length == 2
let parameters = x.GetParameters()
where parameters.Length == 2 &&
parameters[0].ParameterType == typeof(IQueryable<>).MakeGenericType(generics[0]) &&
parameters[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(generics))
select x).Single();
return orderByMethod;
}
Please don't ever use AsQueryable<>(). It doesn't do what you think, and it is totally useless outside unit testing and very specific use cases.
You could use my OrderByString extension. https://www.nuget.org/packages/OrderByString/ It takes strings for sort parameters. The sort parameters strings can be comma-delimited lists of property names, such as "Prop1,Prop2" or it can include a sort order as in "Prop1 DESC, Prop2 ASC".
using OrderByExtensions;
public static IQueryable<T> OrderData<T>(IQueryable<T> data)
{
try
{
Order order = Order.ASC;
var result = Enum.TryParse<Order>(_gridSettings.SortOrder, true, out order);
var sortColumn = _gridSettings.IsSearch ? _gridSettings.SortColumn : _defaultColumn;
data = data.OrderBy(sortColumn + " " + _gridSettings.SortOrder.ToString());
}
catch (Exception ex)
{
log.WriteLog(MethodBase.GetCurrentMethod(), LogLevel.FATAL, ex);
}
return data;
}
OR
You could use the following GetExpressionForProperty method that returns the expected sort expression for OrderBy, OrderByDescending, ThenBy, or ThenByDescending.
private static IQueryable<T> ExpressionSort<T>(Order order, IQueryable<T> data, PropertyInfo property)
{
Expression<Func<T, object>> propertyExpression = GetExpressionForProperty<T>(property);
return order == Order.DESC ? data.OrderByDescending(propertyExpression) : data.OrderBy(propertyExpression);
}
static Expression<Func<TSource, object>> GetExpressionForProperty<TSource>(PropertyInfo propertyInfo)
{
var param = Expression.Parameter(typeof(TSource));
return Expression.Lambda<Func<TSource, object>>(
Expression.Convert(
Expression.Property(param, propertyInfo),
typeof(object)
)
, param);
}
Try using Expression.Convert. Here's a similar question that may give you some more guidance:
Expression of type 'System.Int32' cannot be used for return type 'System.Object'

Categories

Resources