11

Estoy usando EF 4.2, pero espero que esto se aplique a EF 4 y 4.1 también.¿Cómo pasar múltiples expresiones a OrderBy para EF?

me gustaría pasar un múltiplo Expression<Func<TSource, TKey>>IQueryable<T> ya un método y tienen el método de aplicación OrderBy y ThenBy a la IQueryable<T> según corresponda.

He encontrado this answer, y escribió el siguiente método basado en que:

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; 
} 

Esto funciona bien siempre y cuando las propiedades que ORDER BY son string s, pero cuando trato de ordenar por una propiedad int, Obtengo una excepción:

No se ha podido lanzar el tipo 'System.Int32' para escribir 'System.IComparable'. LINQ to Entities solo admite la fundición de tipos primitivos de Entity Data Model.

¿Alguna sugerencia para trabajar alrededor de esto o para un enfoque diferente en conjunto? Consideré pasar un IEnumerable<Expression>, pero luego tendría que averiguar cómo volver al tipo específico (por ejemplo, Expression<Func<User, int>) para llamar al OrderBy.

Respuesta

15

No puedo explicar por qué usar un Int32 no funciona pero usando un string. ¿No son ambos tipos EDM "primitivos" y no implementan ambos IComparable? No entiendo el comportamiento diferente.

De todos modos, parece que es necesario pasar todas las expresiones de la colección con el tipo concreto para que se ordene para evitar la conversión de tipo fallido. En otras palabras, no es un IComparable, pero en lugar de una int, una string, un DateTime, etc.

tuve éxito para lograr esto en la línea de la idea en esta respuesta: How to check for the presence of an OrderBy in a ObjectQuery<T> expression tree

Definir una interfaz que hace no depende del tipo para ordenar por, pero solo el tipo de entidad. (El siguiente ejemplo se generaliza a entidades arbitrarias. Si sólo desea que por User eliminar el parámetro genérico y reemplazar TEntity en los queryables por User.)

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

Definir una implementación de la interfaz que ahora toma el tipo de ordenar por como segundo parámetro genérico:

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); 
    } 
} 

Entonces ApplyOrderBy se vería así:

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; 
} 

Y puede ser utilizado de la siguiente manera:

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 

La necesidad de especificar los parámetros de tipo genérico explícitamente en los casos OrderByExpression no es agradable, pero no pude encontrar una manera para que el compilador infiere los tipos.(Yo estaba esperando que fuera, porque el compilador infiere el User como TEntity de query para el método ApplyOrderBy, lo que esperaba que conoce la TEntity de OrderByExpression (igual User también). Así que el parámetro lambda u debe ser conocido como un User y luego el compilador podría derivar del tipo de UserName como string y desde UserId como int. Pero esta teoría es aparentemente mal. el compilador se queja y quiere tener los tipos genéricos de forma explícita.)

+0

1, Gran respuesta, funciona perfectamente y es exactamente lo que esperaba :) –

+0

He estado luchando con esto, y probablemente me rendiré , pero reconozco que debería ser posible usar Expression >, luego cuando llamas a ApplyOrderBy inspecciona la expresión Lambda, busca el tipo del parámetro y luego construye una nueva expresión Lambda correctamente tipada. Aunque parece demasiado trabajo duro. –

+0

Excelente respuesta, funcionó para mí 'fuera de la caja' –

Cuestiones relacionadas