Construct expression tree with chained properties? - c#

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]);
}

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.

Dynamic Create Func<T,TR> C#

I'm trying to create a dynamic lambda exp to filter some results on my list, but I can't figure out on how to create a dynamic func<,>
//Here I get the type of my object "Produto", but I can have other objects here...Like "Pedido" or another one.
Type type = typeof(Produto);
var param = Expression.Parameter(type, "arg");
var propName = Expression.Property(param, "Codigo");
var constP = Expression.Constant("123");
var nameStartsWith = Expression.Call(
propName,
typeof(string).GetMethod("StartsWith", new[] { typeof(string) }),
constP);
//Here I have to instantiate my Func<T, bool>
//I can't instantiate it with my variable "type" to use on my Where clausule.
//I don't know how to do this in another way, to create my lambda.
var WhereExp = Expression.Lambda<Func<type, bool>>(nameStartsWith, param);
return _uow.produto.GetAll().AsQueryable().Where(WhereExp).ToList();
You need to create a generic method. Something like this (untested):
public Expression<Func<T, bool>> DynamicWhere<T>(string pname, string value) where T : class
{
var param = Expression.Parameter(typeof(T), "arg");
var propName = Expression.Property(param, pname);
var constP = Expression.Constant(value);
var nameStartsWith = Expression.Call(
propName,
typeof(string).GetMethod("StartsWith", new[] { typeof(string) }),
constP);
return Expression.Lambda<Func<T, bool>>(nameStartsWith, param);
}
Then you can use it like this:
var WhereExp = DynamicWhere<Produto>("Codigo", "123");
return _uow.produto.GetAll().AsQueryable().Where(WhereExp).ToList();

Compound expression with variable comparator?

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;
}
}

linq orderby is not working with dynamic value on child table [duplicate]

public static IQueryable<TResult> ApplySortFilter<T, TResult>(this IQueryable<T> query, string columnName)
where T : EntityObject
{
var param = Expression.Parameter(typeof(T), "o");
var body = Expression.PropertyOrField(param,columnName);
var sortExpression = Expression.Lambda(body, param);
return query.OrderBy(sortExpression);
}
Because the type for OrderBy is not inferred from sortExpression I need to specify it something like this at run time:
var sortExpression = Expression.Lambda<T, TSortColumn>(body, param);
Or
return query.OrderBy<T, TSortColumn>(sortExpression);
I don't think this is possible however as TSortColumn can only be determined during runtime.
Is there a way around this?
We did something similar (not 100% the same, but similar) in a LINQ to SQL project. Here's the code:
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));
return source.Provider.CreateQuery<T>(resultExp);
}
We didn't actually use a generic, we had a known class, but it should work on a generic (I've put the generic placeholder where it should be).
Edit: For descending order, pass in OrderByDescending instead of "OrderBy":
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
You can also use Dynamic Linq
Info here
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
C# download here
http://msdn.microsoft.com/en-us/vcsharp/bb894665.aspx
Then just add the using Linq.Dynamic; and you automatically get 2 additional extension methods that can be used like this
return query.OrderBy("StringColumnName");
I've extended your functions to add support for Child Properties.
private static LambdaExpression GenerateSelector<TEntity>(String propertyName, out Type resultType) where TEntity : class
{
// Create a parameter to pass into the Lambda expression (Entity => Entity.OrderByField).
var parameter = Expression.Parameter(typeof(TEntity), "Entity");
// create the selector part, but support child properties
PropertyInfo property;
Expression propertyAccess;
if (propertyName.Contains('.'))
{
// support to be sorted on child fields.
String[] childProperties = propertyName.Split('.');
property = typeof(TEntity).GetProperty(childProperties[0]);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
for (int i = 1; i < childProperties.Length; i++)
{
property = property.PropertyType.GetProperty(childProperties[i]);
propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
}
}
else
{
property = typeof(TEntity).GetProperty(propertyName);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
}
resultType = property.PropertyType;
// Create the order by expression.
return Expression.Lambda(propertyAccess, parameter);
}
private static MethodCallExpression GenerateMethodCall<TEntity>(IQueryable<TEntity> source, string methodName, String fieldName) where TEntity : class
{
Type type = typeof(TEntity);
Type selectorResultType;
LambdaExpression selector = GenerateSelector<TEntity>(fieldName, out selectorResultType);
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName,
new Type[] { type, selectorResultType },
source.Expression, Expression.Quote(selector));
return resultExp;
}
You can use these functions like:
GenerateMethodCall<TEntity>(source, "OrderByDescending", fieldName);
I used your idea for extension method for OrderBy. But in case of "many to many" I am getting error. For example you have table Site, Customer and Customer_site.
For given Site I want to sort by customer name and in OrderBy extension (when I pass "site.customer" where customer is navigation property) I get error in line: propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
This is what I use (with some enhancements :-) ):
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByValues) where TEntity : class
{
IQueryable<TEntity> returnValue = null;
string orderPair = orderByValues.Trim().Split(',')[0];
string command = orderPair.ToUpper().Contains("DESC") ? "OrderByDescending" : "OrderBy";
var type = typeof(TEntity);
var parameter = Expression.Parameter(type, "p");
string propertyName = (orderPair.Split(' ')[0]).Trim();
System.Reflection.PropertyInfo property;
MemberExpression propertyAccess;
if (propertyName.Contains('.'))
{
// support to be sorted on child fields.
String[] childProperties = propertyName.Split('.');
property = typeof(TEntity).GetProperty(childProperties[0]);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
for (int i = 1; i < childProperties.Length; i++)
{
Type t = property.PropertyType;
if (!t.IsGenericType)
{
property = t.GetProperty(childProperties[i]);
}
else
{
property = t.GetGenericArguments().First().GetProperty(childProperties[i]);
}
propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
}
}
else
{
property = type.GetProperty(propertyName);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
}
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
source.Expression, Expression.Quote(orderByExpression));
returnValue = source.Provider.CreateQuery<TEntity>(resultExpression);
if (orderByValues.Trim().Split(',').Count() > 1)
{
// remove first item
string newSearchForWords = orderByValues.ToString().Remove(0, orderByValues.ToString().IndexOf(',') + 1);
return source.OrderBy(newSearchForWords);
}
return returnValue;
}
Regards
Slobodan
It seems that this is the way to do it, now to verify that:
// ***** OrderBy(company => company) *****
// Create an expression tree that represents the expression
// 'whereCallExpression.OrderBy(company => company)'
MethodCallExpression orderByCallExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { queryableData.ElementType, queryableData.ElementType },
whereCallExpression,
Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
// ***** End OrderBy *****
If you are able to add "System.Linq.Dynamic" package then,
Too easy without any complication,
fisrt insatll package "System.Linq.Dynamic" from NuGet package manager then
try as below as your need,
Ex:
public IQueryable<TEntity> GetWithInclude(Expression<Func<TEntity, bool>> predicate,
List<string> sortBy, int pageNo, int pageSize = 12, params string[] include)
{
try
{
var numberOfRecordsToSkip = pageNo * pageSize;
var dynamic = DbSet.AsQueryable();
foreach (var s in include)
{
dynamic.Include(s);
}
return dynamic.OrderBy("CreatedDate").Skip(numberOfRecordsToSkip).Take(pageSize);
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
Hope this will help
I fixed this code a bit: https://stackoverflow.com/a/1670085/5852630
This code works with sequential sorting: first execute "OrderBy", then "ThenBy"(Not "OrderBy"!)
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByValues) where TEntity : class
{
IQueryable<TEntity> returnValue = null;
string[] orderPairs = orderByValues.Trim().Split(',');
Expression resultExpression = source.Expression;
string strAsc = "OrderBy";
string strDesc = "OrderByDescending";
foreach (string orderPair in orderPairs)
{
if (string.IsNullOrWhiteSpace(orderPair))
continue;
string[] orderPairArr = orderPair.Trim().Split(' ');
string propertyName = orderPairArr[0].Trim();
string orderNarrow = orderPairArr.Length > 1 ? orderPairArr[1].Trim() : string.Empty;
string command = orderNarrow.ToUpper().Contains("DESC") ? strDesc : strAsc;
Type type = typeof(TEntity);
ParameterExpression parameter = Expression.Parameter(type, "p");
System.Reflection.PropertyInfo property;
Expression propertyAccess;
if (propertyName.Contains('.'))
{
// support to be sorted on child fields.
String[] childProperties = propertyName.Split('.');
property = typeof(TEntity).GetProperty(childProperties[0]);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
for (int i = 1; i < childProperties.Length; i++)
{
Type t = property.PropertyType;
if (!t.IsGenericType)
{
property = t.GetProperty(childProperties[i]);
}
else
{
property = t.GetGenericArguments().First().GetProperty(childProperties[i]);
}
propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
}
}
else
{
property = type.GetProperty(propertyName);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
}
if (property.PropertyType == typeof(object))
{
propertyAccess = Expression.Call(propertyAccess, "ToString", null);
}
LambdaExpression orderByExpression = Expression.Lambda(propertyAccess, parameter);
resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType == typeof(object) ? typeof(string) : property.PropertyType },
resultExpression, Expression.Quote(orderByExpression));
strAsc = "ThenBy";
strDesc = "ThenByDescending";
}
returnValue = source.Provider.CreateQuery<TEntity>(resultExpression);
return returnValue;
}
Here is my adaptation from #Davy Landman's answer (I wanted an extension method) and I simplified a bit.
public static IQueryable<T> SortBy<T>(this IQueryable<T> source,
String propertyName,
WebControls.SortDirection direction)
{
if (source == null) throw new ArgumentNullException("source");
if (String.IsNullOrEmpty(propertyName)) return source;
// Create a parameter to pass into the Lambda expression
//(Entity => Entity.OrderByField).
var parameter = Expression.Parameter(typeof(T), "Entity");
// create the selector part, but support child properties (it works without . too)
String[] childProperties = propertyName.Split('.');
MemberExpression property = Expression.Property(parameter, childProperties[0]);
for (int i = 1; i < childProperties.Length; i++)
{
property = Expression.Property(property, childProperties[i]);
}
LambdaExpression selector = Expression.Lambda(property, parameter);
string methodName = (direction > 0) ? "OrderByDescending" : "OrderBy";
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName,
new Type[] { source.ElementType, property.Type },
source.Expression, Expression.Quote(selector));
return source.Provider.CreateQuery<T>(resultExp);
}
It can be used like this:
gridview1.DataSource = DbContext.TB_CARS.SortBy("model", SortDirection.Descending);
//OR
gridview1.DataSource = DbContext.TB_CARS.SortBy("owner.first_name", 0);

How do I apply OrderBy on an IQueryable using a string column name within a generic extension method?

public static IQueryable<TResult> ApplySortFilter<T, TResult>(this IQueryable<T> query, string columnName)
where T : EntityObject
{
var param = Expression.Parameter(typeof(T), "o");
var body = Expression.PropertyOrField(param,columnName);
var sortExpression = Expression.Lambda(body, param);
return query.OrderBy(sortExpression);
}
Because the type for OrderBy is not inferred from sortExpression I need to specify it something like this at run time:
var sortExpression = Expression.Lambda<T, TSortColumn>(body, param);
Or
return query.OrderBy<T, TSortColumn>(sortExpression);
I don't think this is possible however as TSortColumn can only be determined during runtime.
Is there a way around this?
We did something similar (not 100% the same, but similar) in a LINQ to SQL project. Here's the code:
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));
return source.Provider.CreateQuery<T>(resultExp);
}
We didn't actually use a generic, we had a known class, but it should work on a generic (I've put the generic placeholder where it should be).
Edit: For descending order, pass in OrderByDescending instead of "OrderBy":
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
You can also use Dynamic Linq
Info here
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
C# download here
http://msdn.microsoft.com/en-us/vcsharp/bb894665.aspx
Then just add the using Linq.Dynamic; and you automatically get 2 additional extension methods that can be used like this
return query.OrderBy("StringColumnName");
I've extended your functions to add support for Child Properties.
private static LambdaExpression GenerateSelector<TEntity>(String propertyName, out Type resultType) where TEntity : class
{
// Create a parameter to pass into the Lambda expression (Entity => Entity.OrderByField).
var parameter = Expression.Parameter(typeof(TEntity), "Entity");
// create the selector part, but support child properties
PropertyInfo property;
Expression propertyAccess;
if (propertyName.Contains('.'))
{
// support to be sorted on child fields.
String[] childProperties = propertyName.Split('.');
property = typeof(TEntity).GetProperty(childProperties[0]);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
for (int i = 1; i < childProperties.Length; i++)
{
property = property.PropertyType.GetProperty(childProperties[i]);
propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
}
}
else
{
property = typeof(TEntity).GetProperty(propertyName);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
}
resultType = property.PropertyType;
// Create the order by expression.
return Expression.Lambda(propertyAccess, parameter);
}
private static MethodCallExpression GenerateMethodCall<TEntity>(IQueryable<TEntity> source, string methodName, String fieldName) where TEntity : class
{
Type type = typeof(TEntity);
Type selectorResultType;
LambdaExpression selector = GenerateSelector<TEntity>(fieldName, out selectorResultType);
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName,
new Type[] { type, selectorResultType },
source.Expression, Expression.Quote(selector));
return resultExp;
}
You can use these functions like:
GenerateMethodCall<TEntity>(source, "OrderByDescending", fieldName);
I used your idea for extension method for OrderBy. But in case of "many to many" I am getting error. For example you have table Site, Customer and Customer_site.
For given Site I want to sort by customer name and in OrderBy extension (when I pass "site.customer" where customer is navigation property) I get error in line: propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
This is what I use (with some enhancements :-) ):
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByValues) where TEntity : class
{
IQueryable<TEntity> returnValue = null;
string orderPair = orderByValues.Trim().Split(',')[0];
string command = orderPair.ToUpper().Contains("DESC") ? "OrderByDescending" : "OrderBy";
var type = typeof(TEntity);
var parameter = Expression.Parameter(type, "p");
string propertyName = (orderPair.Split(' ')[0]).Trim();
System.Reflection.PropertyInfo property;
MemberExpression propertyAccess;
if (propertyName.Contains('.'))
{
// support to be sorted on child fields.
String[] childProperties = propertyName.Split('.');
property = typeof(TEntity).GetProperty(childProperties[0]);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
for (int i = 1; i < childProperties.Length; i++)
{
Type t = property.PropertyType;
if (!t.IsGenericType)
{
property = t.GetProperty(childProperties[i]);
}
else
{
property = t.GetGenericArguments().First().GetProperty(childProperties[i]);
}
propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
}
}
else
{
property = type.GetProperty(propertyName);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
}
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
source.Expression, Expression.Quote(orderByExpression));
returnValue = source.Provider.CreateQuery<TEntity>(resultExpression);
if (orderByValues.Trim().Split(',').Count() > 1)
{
// remove first item
string newSearchForWords = orderByValues.ToString().Remove(0, orderByValues.ToString().IndexOf(',') + 1);
return source.OrderBy(newSearchForWords);
}
return returnValue;
}
Regards
Slobodan
It seems that this is the way to do it, now to verify that:
// ***** OrderBy(company => company) *****
// Create an expression tree that represents the expression
// 'whereCallExpression.OrderBy(company => company)'
MethodCallExpression orderByCallExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { queryableData.ElementType, queryableData.ElementType },
whereCallExpression,
Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
// ***** End OrderBy *****
If you are able to add "System.Linq.Dynamic" package then,
Too easy without any complication,
fisrt insatll package "System.Linq.Dynamic" from NuGet package manager then
try as below as your need,
Ex:
public IQueryable<TEntity> GetWithInclude(Expression<Func<TEntity, bool>> predicate,
List<string> sortBy, int pageNo, int pageSize = 12, params string[] include)
{
try
{
var numberOfRecordsToSkip = pageNo * pageSize;
var dynamic = DbSet.AsQueryable();
foreach (var s in include)
{
dynamic.Include(s);
}
return dynamic.OrderBy("CreatedDate").Skip(numberOfRecordsToSkip).Take(pageSize);
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
Hope this will help
I fixed this code a bit: https://stackoverflow.com/a/1670085/5852630
This code works with sequential sorting: first execute "OrderBy", then "ThenBy"(Not "OrderBy"!)
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByValues) where TEntity : class
{
IQueryable<TEntity> returnValue = null;
string[] orderPairs = orderByValues.Trim().Split(',');
Expression resultExpression = source.Expression;
string strAsc = "OrderBy";
string strDesc = "OrderByDescending";
foreach (string orderPair in orderPairs)
{
if (string.IsNullOrWhiteSpace(orderPair))
continue;
string[] orderPairArr = orderPair.Trim().Split(' ');
string propertyName = orderPairArr[0].Trim();
string orderNarrow = orderPairArr.Length > 1 ? orderPairArr[1].Trim() : string.Empty;
string command = orderNarrow.ToUpper().Contains("DESC") ? strDesc : strAsc;
Type type = typeof(TEntity);
ParameterExpression parameter = Expression.Parameter(type, "p");
System.Reflection.PropertyInfo property;
Expression propertyAccess;
if (propertyName.Contains('.'))
{
// support to be sorted on child fields.
String[] childProperties = propertyName.Split('.');
property = typeof(TEntity).GetProperty(childProperties[0]);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
for (int i = 1; i < childProperties.Length; i++)
{
Type t = property.PropertyType;
if (!t.IsGenericType)
{
property = t.GetProperty(childProperties[i]);
}
else
{
property = t.GetGenericArguments().First().GetProperty(childProperties[i]);
}
propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
}
}
else
{
property = type.GetProperty(propertyName);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
}
if (property.PropertyType == typeof(object))
{
propertyAccess = Expression.Call(propertyAccess, "ToString", null);
}
LambdaExpression orderByExpression = Expression.Lambda(propertyAccess, parameter);
resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType == typeof(object) ? typeof(string) : property.PropertyType },
resultExpression, Expression.Quote(orderByExpression));
strAsc = "ThenBy";
strDesc = "ThenByDescending";
}
returnValue = source.Provider.CreateQuery<TEntity>(resultExpression);
return returnValue;
}
Here is my adaptation from #Davy Landman's answer (I wanted an extension method) and I simplified a bit.
public static IQueryable<T> SortBy<T>(this IQueryable<T> source,
String propertyName,
WebControls.SortDirection direction)
{
if (source == null) throw new ArgumentNullException("source");
if (String.IsNullOrEmpty(propertyName)) return source;
// Create a parameter to pass into the Lambda expression
//(Entity => Entity.OrderByField).
var parameter = Expression.Parameter(typeof(T), "Entity");
// create the selector part, but support child properties (it works without . too)
String[] childProperties = propertyName.Split('.');
MemberExpression property = Expression.Property(parameter, childProperties[0]);
for (int i = 1; i < childProperties.Length; i++)
{
property = Expression.Property(property, childProperties[i]);
}
LambdaExpression selector = Expression.Lambda(property, parameter);
string methodName = (direction > 0) ? "OrderByDescending" : "OrderBy";
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName,
new Type[] { source.ElementType, property.Type },
source.Expression, Expression.Quote(selector));
return source.Provider.CreateQuery<T>(resultExp);
}
It can be used like this:
gridview1.DataSource = DbContext.TB_CARS.SortBy("model", SortDirection.Descending);
//OR
gridview1.DataSource = DbContext.TB_CARS.SortBy("owner.first_name", 0);

Categories

Resources