Múltiple "ordenar por" en LINQ

1582

Tengo dos tablas moviesy primero categoriesobtengo una lista ordenada por Id. De categoría y luego por Nombre .

La tabla de películas tiene tres columnas ID, Name y CategoryID . La tabla de categorías tiene dos columnas ID y Nombre .

Intenté algo como lo siguiente, pero no funcionó.

var movies = _db.Movies.OrderBy( m => { m.CategoryID, m.Name })
Sasha
fuente
Aquí se explica por qué esto no puede funcionar: se supone que la expresión lambda entre paréntesis devuelve un valor que se puede usar para ordenar los elementos: m.CategoryID es un número que se puede usar para ordenar los elementos. Pero "m.CategoryID, m.Name" no tiene sentido en este contexto.
chiccodoro
99
Entonces, ¿por qué estás buscando?
eka808
Si por casualidad desea ordenarlos en orden descendente, este es el camino a seguir.
RBT

Respuestas:

2831

Esto debería funcionar para usted:

var movies = _db.Movies.OrderBy(c => c.Category).ThenBy(n => n.Name)
Nathan W
fuente
44
Gracias por la respuesta, por supuesto ... Pero en lugar de Var movies = _db.Movies.Orderby(c => c.Category).ThenBy(n => n.Name) usar Var movies = _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name) 2 veces "orderBy", ¿por qué el resultado es diferente?
147
@devendra, el resultado es diferente porque el segundo "OrderBy" funciona sobre la colección que es el resultado del primer "OrderBy" y reordena sus artículos
68
¿Cómo demonios he pasado todo este tiempo sin saberlo ThenBy? ( Editar: parece que se introdujo en .NET 4.0, lo que explica cómo pasó desapercibido).
Jordan Gray
13
Esto ha estado allí desde que se agregó LINQ. Esta respuesta es pre .NET 4.0.
Nathan W
10
Sí, llegué a la conclusión de que demasiado rápido basado en 3.5 no está en el menú desplegable de versión en la página de documentación ; Debería haber buscado toda la información de la versión. Gracias por la corrección. :)
Jordan Gray
594

Usando LINQ sintaxis de consulta-sintaxis, puede hacer esto:

var movies = from row in _db.Movies 
             orderby row.Category, row.Name
             select row;

[EDITAR para abordar el comentario] Para controlar el orden de clasificación, use las palabras clave ascending(que es la predeterminada y, por lo tanto, no es particularmente útil) o descending, de esta manera:

var movies = from row in _db.Movies 
             orderby row.Category descending, row.Name
             select row;
Scott Stafford
fuente
1
No hay una manera de alternar entre descendente y no en esta sintaxis ¿existe?
ehdv
1
En realidad, su respuesta es equivalente a _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). Más correcto esfrom row in _db.Movies orderby row.Category descending orderby row.Name select row
Lodewijk
99
@Lodewijk: Creo que tienes eso exactamente al revés. Su ejemplo terminará teniendo row.Name siendo la columna primaria y row.Category secundaria, que es el equivalente de _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). Los dos fragmentos que proporciona son equivalentes entre sí, no a los OP.
Scott Stafford
66
El único inconveniente de usar la sintaxis SQL para Linq es que no todas las funciones son compatibles, la mayoría pero no todas
Joshua G
74

Añadir "nuevo":

var movies = _db.Movies.OrderBy( m => new { m.CategoryID, m.Name })

Eso funciona en mi caja. Devuelve algo que se puede usar para ordenar. Devuelve un objeto con dos valores.

Similar, pero diferente a ordenar por una columna combinada, como sigue.

var movies = _db.Movies.OrderBy( m => (m.CategoryID.ToString() + m.Name))
Alex
fuente
21
Tenga cuidado al usar eso para números.
WoF_Angel
77
Puede usar OrderByDescending y ThenBy, o OrderBy y ThenByDescending, según sus necesidades.
Ali Shah Ahmed
66
Estoy bastante seguro de eso .OrderBy( m => new { m.CategoryID, m.Name })y .OrderBy( m => new { m.Name, m.CategoryID })produciré los mismos resultados en lugar de respetar la prioridad prevista. A veces parecerá que le da el orden que desea simplemente por coincidencia. Además m.CategoryID.ToString() + m.Nameproducirá pedidos incorrectos si CategoryID es un int. Por ejemplo, algo con id = 123, name = 5times aparecerá después de id = 1234, name = something en lugar de antes. Tampoco es ineficiente hacer comparaciones de cadenas donde podrían ocurrir comparaciones int.
AaronLS
77
Cuando trato de ordenar por un tipo anónimo, aparece una excepción ArgumentException con el mensaje "Al menos un objeto debe implementar IComparable". Veo que otros tienen que declarar un comparador cuando hacen esto. Ver stackoverflow.com/questions/10356864/… .
Robert Gowland
44
Esto está absolutamente mal. Ordenar por un nuevo tipo anónimo que no tiene implementación ICompariable no puede funcionar, porque no hay orden en las propiedades de un tipo anónimo. No sabría si ordenar primero CategoryID o Name primero, y mucho menos si se ordenaran en órdenes opuestos.
Triynko
29

use la siguiente línea en su DataContext para registrar la actividad de SQL en el DataContext en la consola; luego podrá ver exactamente lo que sus declaraciones linq solicitan de la base de datos:

_db.Log = Console.Out

Las siguientes declaraciones de LINQ:

var movies = from row in _db.Movies 
             orderby row.CategoryID, row.Name
             select row;

Y

var movies = _db.Movies.OrderBy(m => m.CategoryID).ThenBy(m => m.Name);

producir el siguiente SQL:

SELECT [t0].ID, [t0].[Name], [t0].CategoryID
FROM [dbo].[Movies] as [t0]
ORDER BY [t0].CategoryID, [t0].[Name]

Mientras que, al repetir un OrderBy en Linq, parece revertir la salida SQL resultante:

var movies = from row in _db.Movies 
             orderby row.CategoryID
             orderby row.Name
             select row;

Y

var movies = _db.Movies.OrderBy(m => m.CategoryID).OrderBy(m => m.Name);

producir el siguiente SQL (se cambian Name y CategoryId):

SELECT [t0].ID, [t0].[Name], [t0].CategoryID
FROM [dbo].[Movies] as [t0]
ORDER BY [t0].[Name], [t0].CategoryID
Oliver Slay
fuente
24

He creado algunos métodos de extensión (a continuación) para que no tenga que preocuparse si un IQueryable ya está ordenado o no. Si desea ordenar por múltiples propiedades, simplemente hágalo de la siguiente manera:

// We do not have to care if the queryable is already sorted or not. 
// The order of the Smart* calls defines the order priority
queryable.SmartOrderBy(i => i.Property1).SmartOrderByDescending(i => i.Property2);

Esto es especialmente útil si crea el orden dinámicamente, por ejemplo, desde una lista de propiedades para ordenar.

public static class IQueryableExtension
{
    public static bool IsOrdered<T>(this IQueryable<T> queryable) {
        if(queryable == null) {
            throw new ArgumentNullException("queryable");
        }

        return queryable.Expression.Type == typeof(IOrderedQueryable<T>);
    }

    public static IQueryable<T> SmartOrderBy<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
        if(queryable.IsOrdered()) {
            var orderedQuery = queryable as IOrderedQueryable<T>;
            return orderedQuery.ThenBy(keySelector);
        } else {
            return queryable.OrderBy(keySelector);
        }
    }

    public static IQueryable<T> SmartOrderByDescending<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
        if(queryable.IsOrdered()) {
            var orderedQuery = queryable as IOrderedQueryable<T>;
            return orderedQuery.ThenByDescending(keySelector);
        } else {
            return queryable.OrderByDescending(keySelector);
        }
    }
}
sjkm
fuente
1
¡Esta respuesta es oro! Combinaré el check para queryable.IsOrdered () con la respuesta de esta publicación, para tener un método único que tome una dirección de clasificación: stackoverflow.com/questions/388708
SwissCoder
1
¡De esta manera, la implementación de Linq debería ir en primer lugar! OrderBy está mal diseñado ...
Andrzej Martyna
1
@Marie, claramente no has entendido el caso de uso. ¿Cómo se crea un pedido de forma dinámica a partir de una lista de propiedades, por ejemplo? Esta es la única manera. Por favor, no desestime ni reconsidere. Gracias.
sjkm
Lo suficientemente justo. Todavía no veo el beneficio, pero retiraré mi voto
Marie
¿ nullable datetime
Funcionará
16

Hay al menos una forma más de hacer esto usando LINQ, aunque no es la más fácil. Puede hacerlo usando el OrberBy()método que usa un IComparer. Primero debe implementar un IComparerpara la Movieclase como este:

public class MovieComparer : IComparer<Movie>
{
    public int Compare(Movie x, Movie y)
    {
        if (x.CategoryId == y.CategoryId)
        {
            return x.Name.CompareTo(y.Name);
        }
        else
        {
            return x.CategoryId.CompareTo(y.CategoryId);
        }
    }
}

Luego puede ordenar las películas con la siguiente sintaxis:

var movies = _db.Movies.OrderBy(item => item, new MovieComparer());

Si necesita cambiar el orden a descendente para uno de los elementos, simplemente cambie las x e y dentro del Compare() método MovieComparercorrespondiente.

Prudentcoder
fuente
1
Me gusta que sea más general que entonces, ya que puedes hacer cosas raras con la comparación, incluyendo tener diferentes objetos de comparación con diferentes algoritmos listos para funcionar. Esto es mejor que mi solución preferida antes de aprender acerca de cómo crear una clase que implemente la interfaz IComparable.
Gerard ONeill
1
Desde 2012 (.NET versión 4.5) no tiene que crear una clase MovieComparerusted mismo; en cambio puedes hacer _db.Movies.OrderBy(item => item, Comparer<Movie>.Create((x, y) => { if (x.CategoryId == y.CategoryId) { return x.Name.CompareTo(y.Name); } else { return x.CategoryId.CompareTo(y.CategoryId); } }));. Por supuesto, si prefiere escribir la lógica como una expresión, en lugar de if... else, entonces lamda (x, y) => exprpuede ser más simple.
Jeppe Stig Nielsen
3

Si usa repositorio genérico

> lstModule = _ModuleRepository.GetAll().OrderBy(x => new { x.Level,
> x.Rank}).ToList();

más

> _db.Module.Where(x=> ......).OrderBy(x => new { x.Level, x.Rank}).ToList();
Saint-martin Guillaume
fuente
1
Las expresiones anónimas serán analizadas localmente por el núcleo del marco de la entidad. La expresión LINQ no se pudo traducir y se evaluará localmente.
alhpe