如何使用通用扩展方法中的字符串列名在 IQueryable 上应用 OrderBy?
Posted
技术标签:
【中文标题】如何使用通用扩展方法中的字符串列名在 IQueryable 上应用 OrderBy?【英文标题】:How do I apply OrderBy on an IQueryable using a string column name within a generic extension method? 【发布时间】:2010-09-23 09:05:19 【问题描述】: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);
因为 OrderBy 的类型不是从 sortExpression 推断出来的,所以我需要在运行时指定类似这样的内容:
var sortExpression = Expression.Lambda<T, TSortColumn>(body, param);
或者
return query.OrderBy<T, TSortColumn>(sortExpression);
但我认为这是不可能的,因为 TSortColumn 只能在运行时确定。
有没有办法解决这个问题?
【问题讨论】:
不确定this 是否您在寻找什么,但请看一下。干杯 @JTew 我如何按子句实现第二个订单..say orderby id then by date 【参考方案1】:我们在 LINQ to SQL 项目中做了类似的事情(不是 100% 相同,而是类似)。代码如下:
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);
我们实际上并没有使用泛型,我们有一个已知的类,但它应该适用于泛型(我已将泛型占位符放在它应该在的位置)。
编辑:对于降序,传入OrderByDescending
而不是“OrderBy”:
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] type, property.PropertyType , source.Expression, Expression.Quote(orderByExp));
【讨论】:
嘿,没有问题,我不能给自己分配答案:) 对于降序,传入 "OrderByDescending" 而不是 "OrderBy" MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderByDescending", ... 这工作得很好,但以下只是一个非常好的干净代码示例:***.com/questions/41244/dynamic-linq-orderby 参数values
是干什么用的?
未使用值【参考方案2】:
您也可以使用动态 Linq
信息在这里 http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
C# 在这里下载 http://msdn.microsoft.com/en-us/vcsharp/bb894665.aspx
然后只需添加 using Linq.Dynamic;你会自动获得 2 个额外的扩展方法,可以像这样使用
return query.OrderBy("StringColumnName");
【讨论】:
谢谢,我在 Phil Haack 网站的样本中看到过 Linq.Dynamic,但不确定。周末我会玩这个。 Systems.Linq.Dynamic.dll 可以从这里下载:github.com/kahanu/System.Linq.Dynamic【参考方案3】:我已经扩展了您的功能以添加对子属性的支持。
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;
您可以使用以下功能:
GenerateMethodCall<TEntity>(source, "OrderByDescending", fieldName);
【讨论】:
你是我的英雄!! 要爱聪明的人 我尝试了这段代码,它确实适用于一个孩子,但不能适用于多个孩子,例如它适用于对 x.String 和 x.Object.String 进行排序,但不适用于对 x.Object.Object.String 进行排序。【参考方案4】:我将您的想法用于 OrderBy 的扩展方法。但在“多对多”的情况下,我会出错。例如,您有表 Site、Customer 和 Customer_site。 对于给定的站点,我想按客户名称和 OrderBy 扩展名排序(当我传递“site.customer”时,客户是导航属性)我在行中遇到错误:propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
这是我使用的(带有一些增强功能:-)):
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;
问候
斯洛博丹
【讨论】:
【参考方案5】:似乎this 是这样做的方法,现在验证一下:
// ***** 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 *****
【讨论】:
妈的,落后了 34 秒! :P【参考方案6】:如果您能够添加“System.Linq.Dynamic”包,那么, 太简单了,没有任何复杂性,
然后从 NuGet 包管理器中获取 insatll 包“System.Linq.Dynamic” 根据您的需要尝试如下,
例如:
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);
希望这会有所帮助
【讨论】:
【参考方案7】:我稍微修正了这段代码:https://***.com/a/1670085/5852630
此代码适用于顺序排序:首先执行“OrderBy”,然后执行“ThenBy”(不是“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】:这是我对@Davy Landman的回答(我想要一个扩展方法)的改编,我做了一点简化。
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);
可以这样使用:
gridview1.DataSource = DbContext.TB_CARS.SortBy("model", SortDirection.Descending);
//OR
gridview1.DataSource = DbContext.TB_CARS.SortBy("owner.first_name", 0);
【讨论】:
以上是关于如何使用通用扩展方法中的字符串列名在 IQueryable 上应用 OrderBy?的主要内容,如果未能解决你的问题,请参考以下文章
有啥方法可以扩展包含列表的 pandas Dataframe 中的列并从列表值本身中获取列名?