Related
When I have queryable such as
var query = dbSet.AsQueryable();
and I would like to dynamically set orderBy using reflection
var orderBy = "School.Name"
query = query.OrderBy("School.Name");
var data = await query.ToListAsync()
I do have extension to support order like this using reflection as following:
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
return source.OrderBy(ToLambda<T>(propertyName));
}
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName);
var propAsObject = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(propAsObject, parameter);
}
Problem is to create lambda for inner object property such as School.Name. This solution works for Name attribute on Dbset object, but not on joined.
Goal is to modify ToLambda method to support joinned or inners properties.
using
query.OrderBy("School.Name");
// instead of
query.OrderBy(m => m.School.Name );
I have solved it as following:
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
if (propertyName.Contains('.'))
{
var parameter = Expression.Parameter(typeof(T));
var property = NestedProperty(parameter, propertyName.Split('.'));
var propAsObject = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(propAsObject, parameter);
}
else
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName.ToLower());
var propAsObject = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(propAsObject, parameter);
}
}
private static MemberExpression NestedProperty(Expression propertyHolder, params string[] propertyPath)
{
MemberExpression memberExpression = Expression.Property(propertyHolder, propertyPath[0]);
foreach (var member in propertyPath.Skip(1))
{
memberExpression = Expression.Property(memberExpression, member);
}
return memberExpression;
}
I try to implement e dynamic Funx expression from string
Expression<Func<CustomerDto, object>> expr = (src) => src.Customer.Id;
Func<CustomerDto, object> delg = expr.Compile();
var id = delg.Invoke(customerListDtos[0]);
and return the id (es. 123)
So now I try to create expression from string
public Expression<Func<T, object>> GetLambda<T>(string property)
{
var param = Expression.Parameter(typeof(T), "p");
Expression body = param;
foreach (var member in property.Split('.'))
{
body = Expression.PropertyOrField(body, member);
}
//return Expression.Lambda(body, param);
//Expression parent = Expression.Property(param, property);
Expression parent = Expression.Lambda(body, param);
//if (!parent.Type.IsValueType)
//{
// return Expression.Lambda<Func<T, object>>(parent, param);
//}
var convert = Expression.Convert(parent, typeof(object));
return Expression.Lambda<Func<T, object>>(convert, param);
}
So if I call the result from GetLambda the output is not 123 but is
var data = GetLambda<CustomerDto>("Customer.Id");
Func<CustomerDto, object> g = data.Compile();
var id = g(customerListDtos[0]);
the result is
{Method = {Int64 lambda_method(System.Runtime.CompilerServices.Closure, ef.Customer.CustomerDto)}}
You are calling Expression.Labmda twice, essentially wrapping one func into another. Try this instead:
public static Expression<Func<T, object>> GetLambda<T>(string property) {
var param = Expression.Parameter(typeof(T), "p");
Expression body = param;
foreach (var member in property.Split('.')) {
body = Expression.PropertyOrField(body, member);
}
var convert = Expression.Convert(body, typeof(object));
return (Expression<Func<T, object>>) Expression.Lambda(convert, param);
}
I need to call below ExpFunction with reflection:
class Program
{
static void Main(string[] args)
{
ExpClass<TestClass> obj = new ExpClass<TestClass>();
//without reflection
obj.ExpFunction(f => f.Col);
//with reflection
UsingReflection<TestClass>(obj, typeof(TestClass).GetProperty("Col"));
}
}
public class TestClass
{
public string Col { get; set; }
}
public class ExpClass<T>
{
public string ExpFunction(Expression<Func<T, object>> propertyMap)
{
return "success";
}
}
Here is what I did
static void UsingReflection<T>(ExpClass<T> obj, PropertyInfo Property)
{
ParameterExpression parameter = Expression.Parameter(typeof(T), "i");
MemberExpression property = Expression.Property(parameter, Property);
var propertyExpression = Expression.Lambda(property, parameter);
var method = typeof(ExpClass<T>).GetMethod("ExpFunction").MakeGenericMethod(typeof(T));
method.Invoke(obj, new object[] { propertyExpression });
}
But During invoke it says:
Object of type 'System.Linq.Expressions.Expression`1[System.Func`2[ExpressionTest.TestClass,System.String]]'
cannot be converted to type 'System.Linq.Expressions.Expression`1[System.Func`2[ExpressionTest.TestClass,System.Object]]'.
It is probably because ExpFunction accepts Expression<Func<T, object>>. And TestClass.Col is a string.
So how can I do it?
There are two problems, you're not casting the property to Object and you call MakeGenericMethod on a method which is not generic at all.
static void UsingReflection<T>(ExpClass<T> obj, PropertyInfo Property)
{
ParameterExpression parameter = Expression.Parameter(typeof(T), "i");
MemberExpression property = Expression.Property(parameter, Property);
var castExpression = Expression.TypeAs(property, typeof(object));
var propertyExpression = Expression.Lambda(castExpression, parameter);
var method = typeof(ExpClass<T>).GetMethod("ExpFunction");
method.Invoke(obj, new object[] { propertyExpression });
}
The ExpFunction is not generic, so you shouldn't .MakeGenericMethod(typeof(T)) it.
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);
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);