如何为EF传递多个表达式到OrderBy
本文关键字:表达式 OrderBy EF | 更新日期: 2023-09-27 18:15:05
我正在使用EF 4.2,但我希望这也适用于EF 4和4.1。
我想将一个IQueryable<T>
和多个Expression<Func<TSource, TKey>>
传递给一个方法,并让该方法酌情将OrderBy
和ThenBy
应用于IQueryable<T>
。
我找到了这个答案,并在此基础上写下了下面的方法:
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;
}
只要我排序的属性是string
s,这就可以正常工作,但是当我尝试按int
属性排序时,我得到一个异常:
无法强制转换类型"System"。Int32'来输入' system . iccomparable '。LINQ to Entities只支持转换实体数据模型的基本类型。
有什么建议来解决这个问题,或者采用不同的方法吗?我考虑过传入IEnumerable<Expression>
,但随后需要弄清楚如何转换回特定类型(例如Expression<Func<User, int>
)以调用OrderBy
。
我无法解释为什么使用Int32
不工作,但使用string
。不是两个EDM"原始"类型和不都实现IComparable
?我不理解这种不同的行为。
IComparable
,而是int
, string
, DateTime
等。
我已经成功地实现了这一点,沿着这个答案的思路:如何检查objectquery中order的存在
定义一个接口,使不依赖于要排序的类型,而只依赖于实体类型。(下面的例子可以推广到任意实体。如果您只希望对User
这样做,则删除泛型参数并将查询中的TEntity
替换为User
。
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, desc
var result = queryWithOrderBy.ToList(); // didn't throw an exception for me
在OrderByExpression
实例中显式指定泛型类型参数的需要并不好,但我找不到一种方法让编译器推断类型。(我希望它会,因为编译器从ApplyOrderBy
方法的query
推断User
为TEntity
,然后我期望它知道OrderByExpression
的TEntity
(也等于User
)。因此,lambda参数u
应该被称为User
,然后编译器可以从UserName
派生类型为string
,从UserId
派生类型为int
。但这个理论显然是错误的。编译器抱怨并希望显式地使用泛型类型。)