Múltiplo "ordenar por" no LINQ

1582

Tenho duas tabelas moviese categoriesrecebo uma lista ordenada por categoryID primeiro e depois por Name .

A tabela de filmes possui três IDs de colunas , Nome e ID da categoria . A tabela de categorias possui duas colunas ID e Nome .

Tentei algo como o seguinte, mas não funcionou.

var movies = _db.Movies.OrderBy( m => { m.CategoryID, m.Name })
Sasha
fonte
Aqui está o porquê disso não funcionar: A expressão lambda entre parênteses deve retornar um valor que pode ser usado para ordenar os itens: m.CategoryID é um número que pode ser usado para ordenar os itens. Mas "m.CategoryID, m.Name" não faz sentido nesse contexto.
Chiccodoro 22/10/10
9
.Então, por que você está procurando?
eka808
Se por acaso você quiser classificá-los em ordem decrescente, aqui está o caminho a seguir.
RBT

Respostas:

2831

Isso deve funcionar para você:

var movies = _db.Movies.OrderBy(c => c.Category).ThenBy(n => n.Name)
Nathan W
fonte
4
Obrigado pela resposta, é claro ... Mas, em vez 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 vezes "orderBy", por que o resultado é diferente?
147
@devendra, o resultado é diferente porque segundo "OrderBy" trabalha sobre a coleção que é resultado do primeiro "OrderBy" e reordenar seus itens
68
Como diabos eu passei esse tempo todo sem saber ThenBy?! ( Edit: parece que foi introduzido no .NET 4.0, que explica como ele passou por mim despercebido.)
Jordan Gray
13
Isso existe desde que o LINQ foi adicionado. Esta resposta é anterior ao .NET 4.0.
Nathan W
10
Sim, concluí que às pressas, com base no 3.5, não estar na lista suspensa da versão na página de documentação ; Eu deveria ter procurado toda a informação da versão. Obrigado pela correção. :)
Jordan Gray
594

Usando LINQ não-lambda, sintaxe de consulta, você pode fazer isso:

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

[EDITAR para endereçar o comentário] Para controlar a ordem de classificação, use as palavras-chave ascending(que é o padrão e, portanto, não são particularmente úteis) ou descending, assim:

var movies = from row in _db.Movies 
             orderby row.Category descending, row.Name
             select row;
Scott Stafford
fonte
1
Não existe uma maneira de alternar entre descendente e não nesta sintaxe, existe?
ehdv
1
Na verdade, sua resposta é equivalente a _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). O mais correto éfrom row in _db.Movies orderby row.Category descending orderby row.Name select row
Lodewijk
9
@Lodewijk: Eu acredito que você tem isso exatamente ao contrário. Seu exemplo acabará tendo row.Name sendo a coluna e a linha principais.Category secundário, que é o equivalente a _db.Movies.Orderby(c => c.Category).OrderBy(n => n.Name). Os dois trechos que você fornece são equivalentes entre si, não aos OP.
Scott Stafford
6
A única desvantagem de se utilizar a sintaxe SQL para Linq é que nem todas as funções são suportadas, a maioria, mas não todos
Joshua G
74

Adicionar novo":

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

Isso funciona na minha caixa. Ele retorna algo que pode ser usado para classificar. Retorna um objeto com dois valores.

Semelhante, mas diferente da classificação por uma coluna combinada, a seguir.

var movies = _db.Movies.OrderBy( m => (m.CategoryID.ToString() + m.Name))
Alex
fonte
21
Tenha cuidado ao usar isso para números.
WoF_Angel
7
Você pode usar OrderByDescending e ThenBy ou OrderBy e ThenByDescending, dependendo da sua necessidade.
Ali Shah Ahmed
6
Tenho certeza disso .OrderBy( m => new { m.CategoryID, m.Name })e .OrderBy( m => new { m.Name, m.CategoryID })produzirá os mesmos resultados, em vez de respeitar a prioridade pretendida. Às vezes, parece que você recebe a ordem que deseja puramente por coincidência. Além disso m.CategoryID.ToString() + m.Name, produzirá pedidos incorretos se o CategoryID for um int. Por exemplo, algo com id = 123, nome = 5 vezes, aparecerá após id = 1234, nome = algo em vez de antes. Também não é ineficiente fazer comparações de strings nas quais comparações int possam ocorrer.
AaronLS
7
Quando tento ordenar por um tipo anônimo, recebo uma ArgumentException com a mensagem "Pelo menos um objeto deve implementar IComparable". Vejo outros tendo que declarar um comparador ao fazer isso. Consulte stackoverflow.com/questions/10356864/… .
Robert Gowland 31/07
4
Isto está absolutamente errado. A ordenação por um novo tipo anônimo que não possui implementação ICompariable não pode funcionar, porque não há ordem nas propriedades de um tipo anônimo. Não saberia se classificaria primeiro o CategoryID ou o Name primeiro, muito menos se eles fossem classificados em ordens opostas.
Triynko
29

use a seguinte linha em seu DataContext para registrar a atividade SQL no DataContext no console - para poder ver exatamente o que suas instruções linq estão solicitando no banco de dados:

_db.Log = Console.Out

As seguintes instruções LINQ:

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

E

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

produza o seguinte SQL:

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

Considerando que, repetir um OrderBy no Linq, parece inverter a saída SQL resultante:

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

E

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

produza o seguinte SQL (Name e CategoryId são alternados):

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

Eu criei alguns métodos de extensão (abaixo) para que você não precise se preocupar se um IQueryable já está encomendado ou não. Se você deseja solicitar por várias propriedades, faça o seguinte:

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

Isso é especialmente útil se você criar a ordem dinamicamente, fe a partir de uma lista de propriedades para classificar.

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
fonte
1
Esta resposta é ouro! Vou combinar a verificação de queryable.IsOrdered () com a resposta desta postagem, para ter um único método que toma uma direção de classificação: stackoverflow.com/questions/388708
SwissCoder 14/03/19
1
Desta forma, a implementação do Linq deve ir em primeiro lugar! OrderBy é mal projetado ...
Andrzej Martyna
1
@ Marie, você claramente não entendeu o caso de uso. Como você cria um pedido dinamicamente a partir de uma lista de propriedades, por exemplo? Este é o único caminho. Por favor, não faça voto negativo e reconsidere. Obrigado.
Sjkm 17/0918
Justo. Eu ainda não ver o benefício, mas vou levar minha parte traseira do voto
Marie
isso funcionará com #nullable datetime
aggie 27/07/19
16

Há pelo menos mais uma maneira de fazer isso usando o LINQ, embora não seja o mais fácil. Você pode fazer isso usando o OrberBy()método que usa um IComparer. Primeiro você precisa implementar um IComparerpara a Movieclasse 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);
        }
    }
}

Depois, você pode solicitar os filmes com a seguinte sintaxe:

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

Se você precisar alterar a ordem para decrescente para um dos itens, basta alternar x e y dentro do Compare() método correspondente MovieComparer.

prudentcoder
fonte
1
Eu gosto disso como sendo mais geral do que então, já que você pode fazer coisas estranhas com a comparação, incluindo ter objetos de comparação diferentes com algoritmos diferentes prontos para serem usados. Isso é melhor do que minha solução preferida antes de aprender sobre o assunto, que era criar uma classe que implementa a interface IComparable.
precisa saber é o seguinte
1
Desde 2012 (.NET versão 4.5), você não precisa criar uma classe MovieComparer; em vez disso você pode fazer _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); } }));. Obviamente, se você preferir escrever a lógica como uma expressão, em vez de if... else, o lamda (x, y) => exprpode ser mais simples.
Jeppe Stig Nielsen
3

Se usar repositório genérico

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

outro

> _db.Module.Where(x=> ......).OrderBy(x => new { x.Level, x.Rank}).ToList();
Saint-Martin Guillaume
fonte
1
Expressões anônimas serão analisadas localmente pelo núcleo da estrutura da entidade. A expressão LINQ não pôde ser traduzida e será avaliada localmente.
Alhpe 26/03/19