动态创建LINQ到实体OrderBy表达式

我正在尝试动态添加orderby表达式。 但是当执行下面的查询时,我得到以下exception:

System.NotSupportedException:无法创建类型为“Closure type”的常量值。 在此上下文中仅支持基本类型(例如Int32,String和Guid’)。

奇怪的是,我只是查询那些原始类型。

string sortBy = HttpContext.Current.Request.QueryString["sidx"]; ParameterExpression prm = Expression.Parameter(typeof(buskerPosting), "posting"); Expression orderByProperty = Expression.Property(prm, sortBy); // get the paged records IQueryable query = (from posting in be.buskerPosting where posting.buskerAccount.cmsMember.nodeId == m.Id orderby orderByProperty //orderby posting.Created select new PostingListItemDto { Set = posting }).Skip((page - 1) * pageSize).Take(pageSize); 

希望有人能对此有所了解!

由于它们的翻译方式,你基本上不能使用这样的查询表达式。 但是,您可以使用扩展方法显式执行此操作:

 string sortBy = HttpContext.Current.Request.QueryString["sidx"]; ParameterExpression prm = Expression.Parameter(typeof(buskerPosting), "posting"); Expression orderByProperty = Expression.Property(prm, sortBy); // get the paged records IQueryable query = be.buskerPosting .Where(posting => posting.buskerAccount.cmsMember.nodeId == m.Id) .OrderBy(orderByExpression) .Select(posting => new PostingListItemDto { Set = posting }) .Skip((page - 1) * pageSize) .Take(pageSize); 

棘手的一点是获得正确的表达式树类型 – 这将进入编辑:)

编辑:由于各种原因,编辑将有所延迟。 基本上你可能需要使用reflection调用generics方法,因为Queryable.OrderBy需要一个通用的Expression> ,虽然看起来你在编译时知道类型,但你可能不知道密钥类型。 如果您确实知道它总是按(例如)int排序,您可以使用:

 Expression orderByProperty = Expression.Property(prm, sortBy); var orderByExpression = Expression.Lambda> (orderByProperty, new[] { prm }); 

编辑:好的,看起来我毕竟有时间。 这是使用reflection调用OrderBy的简短示例:

 using System; using System.Reflection; using System.Linq; using System.Linq.Expressions; public class Test { static void Main() { string[] names = { "Jon", "Holly", "Tom", "Robin", "Will" }; var query = names.AsQueryable(); query = CallOrderBy(query, "Length"); foreach (var name in query) { Console.WriteLine(name); } } private static readonly MethodInfo OrderByMethod = typeof(Queryable).GetMethods() .Where(method => method.Name == "OrderBy") .Where(method => method.GetParameters().Length == 2) .Single(); public static IQueryable CallOrderBy (IQueryable source, string propertyName) { ParameterExpression parameter = Expression.Parameter(typeof(TSource), "posting"); Expression orderByProperty = Expression.Property(parameter, propertyName); LambdaExpression lambda = Expression.Lambda(orderByProperty, new[] { parameter }); Console.WriteLine(lambda); MethodInfo genericMethod = OrderByMethod.MakeGenericMethod (new[] { typeof(TSource), orderByProperty.Type }); object ret = genericMethod.Invoke(null, new object[] {source, lambda}); return (IQueryable) ret; } } 

您可以轻松地将CallOrderBy重构为扩展方法(例如OrderByProperty ),如下所示:

 public static class ReflectionQueryable { private static readonly MethodInfo OrderByMethod = typeof(Queryable).GetMethods() .Where(method => method.Name == "OrderBy") .Where(method => method.GetParameters().Length == 2) .Single(); public static IQueryable OrderByProperty (this IQueryable source, string propertyName) { ParameterExpression parameter = Expression.Parameter(typeof(TSource), "posting"); Expression orderByProperty = Expression.Property(parameter, propertyName); LambdaExpression lambda = Expression.Lambda(orderByProperty, new[] { parameter }); Console.WriteLine(lambda); MethodInfo genericMethod = OrderByMethod.MakeGenericMethod (new[] { typeof(TSource), orderByProperty.Type }); object ret = genericMethod.Invoke(null, new object[] {source, lambda}); return (IQueryable) ret; } } 

您的原始代码将变为:

 string sortBy = HttpContext.Current.Request.QueryString["sidx"]; // get the paged records IQueryable query = be.buskerPosting .Where(posting => posting.buskerAccount.cmsMember.nodeId == m.Id) .OrderByProperty(sortBy) .Select(posting => new PostingListItemDto { Set = posting }) .Skip((page - 1) * pageSize) .Take(pageSize); 

(对于涉及水平滚动条的格式化道歉……如果有人关心,我会稍后重新格式化。如果你有足够的代表,你可以为我做这件事;)

我想用Jon的上述答案作为起点分享我的实现。 在这种情况下,不是通过来自表示层的字符串属性名称进行排序(因为此问题的标题不是特定于此),我正构建一个Entity Framework数据层并希望允许其使用者指定顺序作为lambda表达式的属性。 IE而不是传递"sidx" ,我希望能够使用p => p.sidx 。 我还希望能够通过属性传递无限数量的顺序,并能够指定升序或降序。

那么我的方法可以接受像Expression>这样的lambda表达式。 这让我按照我想要的方式调用它,但问题是,除非第二个generics参数是强类型的,否则Entity Framework无法将表达式转换为SQL。 OrderBy扩展方法需要两个通用参数:T – 属性所属的类型,以及TKey – 属性返回的类型。 所以第一步是修改Jon的例子,将给定的Expression>转换为Expression> (一旦我们在查询的上下文中工作,我们就可以确定类型TKey ):

 internal static IQueryable OrderByDynamic(this IQueryable source, Expression> sortExp) { //We need to convert the key selector from Expression> to a strongly typed Expression> //in order for Entity Framework to be able to translate it to SQL MemberExpression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExp.Body) as MemberExpression; ParameterExpression sourceParam = sortExp.Parameters[0]; LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam }); MethodInfo orderByMethod = OrderByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type }); //Call OrderBy or OrderByDescending on the source IQueryable return (IQueryable)orderByMethod.Invoke(null, new object[] { source, orderByLamda }); } 

正如我所提到的,我希望通过键选择器接受无限数量的顺序,并且还能够指定升序或降序方向,因此我为Expression>创建了一个包装类,我将其命名为DynamicSortExpression:

 public class DynamicSortExpression { ///  /// Creates a new ascending DynamicSortExpression ///  /// A MemberExpression identifying the property to sort on public DynamicSortExpression(Expression> keySelector) : this(keySelector, false) { } public DynamicSortExpression(Expression> keySelector, bool descending) { this.KeySelector = keySelector; this.Desc = descending; } ///  /// Gets the expression that selects the property of T to sort on ///  public Expression> KeySelector { get; } ///  /// Gets sort expression is in ascending or descending order ///  public bool Desc { get; } } 

然后我更新了扩展方法以接受这种类型并为OrderBy创建了一个重载,它接收List>并使用扩展方法将它们List>添加到查询中。 这是最终结果:

 public static class Extensions { private static readonly MethodInfo OrderByMethod = typeof(Queryable).GetMethods() .Where(method => method.Name == "OrderBy") .Where(method => method.GetParameters().Length == 2) .Single(); private static readonly MethodInfo OrderByDescMethod = typeof(Queryable).GetMethods() .Where(method => method.Name == "OrderByDescending") .Where(method => method.GetParameters().Length == 2) .Single(); private static readonly MethodInfo ThenByMethod = typeof(Queryable).GetMethods() .Where(method => method.Name == "ThenBy") .Where(method => method.GetParameters().Length == 2) .Single(); private static readonly MethodInfo ThenByDescMethod = typeof(Queryable).GetMethods() .Where(method => method.Name == "ThenByDescending") .Where(method => method.GetParameters().Length == 2) .Single(); internal static IQueryable OrderBy(this IQueryable sourceQuery, List> orderBy) { bool isFirst = true; foreach (var sortExpression in orderBy) { if (isFirst) { sourceQuery = sourceQuery.OrderByDynamic(sortExpression); isFirst = false; } else sourceQuery = sourceQuery.ThenByDynamic(sortExpression); } return sourceQuery; } internal static IQueryable OrderByDynamic(this IQueryable source, DynamicSortExpression sortExpression) { //We need to convert the key selector from Expression> to a strongly typed Expression> //in order for Entity Framework to be able to translate it to SQL MemberExpression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExpression.KeySelector.Body) as MemberExpression; ParameterExpression sourceParam = sortExpression.KeySelector.Parameters[0]; LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam }); MethodInfo orderByMethod = sortExpression.Desc ? OrderByDescMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type }) : OrderByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type }); //Call OrderBy or OrderByDescending on the source IQueryable return (IQueryable)orderByMethod.Invoke(null, new object[] { source, orderByLamda }); } internal static IQueryable ThenByDynamic(this IQueryable source, DynamicSortExpression sortExpression) { //We need to convert the key selector from Expression> to a strongly typed Expression> //in order for Entity Framework to be able to translate it to SQL Expression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExpression.KeySelector.Body) as MemberExpression; ParameterExpression sourceParam = sortExpression.KeySelector.Parameters[0]; LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam }); MethodInfo orderByMethod = sortExpression.Desc ? ThenByDescMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type }) : ThenByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type }); //Call OrderBy or OrderByDescending on the source IQueryable return (IQueryable)orderByMethod.Invoke(null, new object[] { source, orderByLamda }); } } 

现在我的数据层可以有像List GetList(Expression> where, params DynamicSortExpression[] orderBy) ,可以像

 new MyClass().GetList(p => p.FirstName == "Billy", //where clause new DynamicSortExpression(p => p.FirstName), new DynamicSortExpression(p => p.LastName, true)); 

RemoveConvert方法是我从EntityFramework源代码中拉出来以递归方式从MemberExpression中删除转换调用的方法:

 internal static Expression RemoveConvert(Expression expression) { System.Diagnostics.Debug.Assert(expression != null); while ((expression != null) && (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.ConvertChecked)) { expression = RemoveConvert(((UnaryExpression)expression).Operand); } return expression; } 

我希望这是有帮助的! 谢谢乔恩!