可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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));
回答2:
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\");
回答3:
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);
回答4:
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
回答5:
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 *****
回答6:
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
回答7:
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;
}
回答8:
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);