C# · 12月 23, 2021

c# – 如何将多个表达式传递给EFB的OrderBy?

我使用EF 4.2,但我预计这将适用于EF 4和4.1.

我想传递一个IQueryable< T>和多个表达式< Func< TSource,TKey>>一种方法,并且该方法将OrderBy和ThenBy应用于IQueryable< T>作为适当的.

我发现了this answer,并根据下面的方法写下:

public IQueryable<User> ApplyOrderBy(IQueryable<User> query,IEnumerable<Expression<Func<User,IComparable>>> orderBy){ if (orderBy == null) { return query; } IOrderedQueryable<User> output = null; foreach(var expression in orderBy) { if (output == null) { output = query.OrderBy(expression); } else { output = output.ThenBy(expression); } } return output ?? query;}

只要我订单的属性是字符串,但是当我尝试通过int属性排序时,我会得到一个例外:

Unable to cast the type ‘system.int32’ to type ‘System.IComparable’. LINQ to Entities only supports casting Entity Data Model primitive types.

要解决这个问题的任何建议,还是一个不同的方法呢?我认为传递一个IEnumerable< Expression>但是然后将需要弄清楚如何转回到调用OrderBy的特定类型(例如,Expression< Func< User,int>).

解决方法 我无法解释为什么使用Int32不起作用但使用字符串.不是EDM都是“原始”类型,并不能同时实现IComparable?我不明白不同的行为.

无论如何,似乎有必要使用具体类型传递集合中的每个表达式,以避免出现失败的类型转换.换句话说,不是一个IComparable,而是一个int,一个字符串,一个DateTime等.

在这个答案中,我已经成功地实现了这一点:How to check for the presence of an OrderBy in a ObjectQuery<T> expression tree

定义一个不依赖于类型进行排序但不仅仅依赖实体类型的接口. (下面的例子被推广到任意实体,如果只需要用户删除通用参数,并用User替换可查询的TEntity.)

public interface IOrderByExpression<TEntity> where TEntity : class{ IOrderedQueryable<TEntity> ApplyOrderBy(IQueryable<TEntity> query); IOrderedQueryable<TEntity> ApplyThenBy(IOrderedQueryable<TEntity> query);}

定义该接口的实现,该接口现在将类型排序为第二个通用参数:

public class OrderByExpression<TEntity,TOrderBy> : IOrderByExpression<TEntity> where TEntity : class{ private Expression<Func<TEntity,TOrderBy>> _expression; private bool _descending; public OrderByExpression(Expression<Func<TEntity,TOrderBy>> expression,bool descending = false) { _expression = expression; _descending = descending; } public IOrderedQueryable<TEntity> ApplyOrderBy( IQueryable<TEntity> query) { if (_descending) return query.OrderByDescending(_expression); else return query.OrderBy(_expression); } public IOrderedQueryable<TEntity> ApplyThenBy( IOrderedQueryable<TEntity> query) { if (_descending) return query.ThenByDescending(_expression); else return query.ThenBy(_expression); }}

那么ApplyOrderBy将如下所示:

public IQueryable<TEntity> ApplyOrderBy<TEntity>(IQueryable<TEntity> query,params IOrderByExpression<TEntity>[] orderByExpressions) where TEntity : class{ if (orderByExpressions == null) return query; IOrderedQueryable<TEntity> output = null; foreach (var orderByExpression in orderByExpressions) { if (output == null) output = orderByExpression.ApplyOrderBy(query); else output = orderByExpression.ApplyThenBy(output); } return output ?? query;}

它可以用如下方式使用:

var query = context.Users … ;var queryWithOrderBy = ApplyOrderBy(query,new OrderByExpression<User,string>(u => u.UserName),// a string,asc new OrderByExpression<User,int>(u => u.UserId,true)); // an int,descvar result = queryWithOrderBy.ToList(); // didn’t throw an exception for me

在OrderByExpression实例中明确指定泛型类型参数的需求不是很好,但是我找不到一种方法,以便编译器推断类型. (我希望这样做,因为编译器将User作为TEntity从ApplyOrderBy方法的查询中推断出来,所以我预计它知道OrderByExpression的TEntity(等于User),所以lambda参数u应该被称为用户然后编译器可以从UserName中导出类型为string,从UserId导出为int,但是这个理论显然是错误的,编译器抱怨并希望明确地显示泛型类型)