Ordem LINQ por coluna nula, em que a ordem é crescente e os nulos devem ser os últimos

141

Estou tentando classificar uma lista de produtos pelo preço.

O conjunto de resultados precisa listar produtos por preço, de baixo a alto, pela coluna LowestPrice. No entanto, esta coluna é anulável.

Posso classificar a lista em ordem decrescente da seguinte forma:

var products = from p in _context.Products
   where p.ProductTypeId == 1
   orderby p.LowestPrice.HasValue descending
   orderby p.LowestPrice descending
   select p;

// returns:    102, 101, 100, null, null

No entanto, não consigo descobrir como classificar isso em ordem crescente.

// i'd like: 100, 101, 102, null, null
sf.
fonte
11
orderby p.LowestPrice ?? Int.MaxValue;é uma maneira simples.
carteiro
3
@PostMan: Sim, é simples, alcança o resultado certo, mas OrderByDescending, ThenByé mais claro.
jason
@ Jason, sim, eu não sabia que a sintaxe para o orderby, e do lado tem monitorado procurando por ele :)
carteiro

Respostas:

160

Tente colocar as duas colunas na mesma ordem, por.

orderby p.LowestPrice.HasValue descending, p.LowestPrice

Caso contrário, cada pedido será uma operação separada na coleção, reordenando-o a cada vez.

Isso deve ordenar os que têm um valor primeiro, "depois" a ordem do valor.

DaveShaw
fonte
21
Erro comum, as pessoas fazem o mesmo com a sintaxe Lamda - usando .OrderBy duas vezes em vez de .ThenBy.
DaveShaw
1
Isso funcionou para ordenar campos com valores nos campos superior e nulo na parte inferior; usei isso: A orderby p.LowestPrice == null, p.LowestPrice ascending esperança ajuda alguém.
shaijut 27/09/2015
@DaveShaw obrigado pela dica - especialmente o comentário um - muito arrumado - adorei
Demetris Leptos
86

Realmente ajuda a entender a sintaxe da consulta LINQ e como ela é convertida em chamadas de método LINQ.

Acontece que

var products = from p in _context.Products
               where p.ProductTypeId == 1
               orderby p.LowestPrice.HasValue descending
               orderby p.LowestPrice descending
               select p;

será traduzido pelo compilador para

var products = _context.Products
                       .Where(p => p.ProductTypeId == 1)
                       .OrderByDescending(p => p.LowestPrice.HasValue)
                       .OrderByDescending(p => p.LowestPrice)
                       .Select(p => p);

Isso não é enfaticamente o que você deseja. Isso classifica por Product.LowestPrice.HasValueem descendingordem e, em seguida, re-ordena toda a coleção por Product.LowestPriceemdescending ordem.

O que você quer é

var products = _context.Products
                       .Where(p => p.ProductTypeId == 1)
                       .OrderByDescending(p => p.LowestPrice.HasValue)
                       .ThenBy(p => p.LowestPrice)
                       .Select(p => p);

que você pode obter usando a sintaxe da consulta,

var products = from p in _context.Products
               where p.ProductTypeId == 1
               orderby p.LowestPrice.HasValue descending,
                       p.LowestPrice
               select p;

Para detalhes das traduções da sintaxe da consulta para chamadas de método, consulte a especificação do idioma. Seriamente. Leia-o.

Jason
fonte
4
+1 ou apenas ... não escreva a sintaxe da consulta LINQ :) Boa explicação, no entanto
sehe,
18

A solução para valores de string é realmente estranha:

.OrderBy(f => f.SomeString == null).ThenBy(f => f.SomeString) 

O único motivo que funciona é porque a primeira expressão OrderBy(), classifica boolvalores: true/ false. falseO resultado segue primeiro o trueresultado (nulos) e ThenBy()classifica os valores não nulos em ordem alfabética.

Então, eu prefiro fazer algo mais legível como este:

.OrderBy(f => f.SomeString ?? "z")

Se SomeStringfor nulo, será substituído por "z"e, em seguida, classificar tudo em ordem alfabética.

NOTA: Esta não é uma solução definitiva, pois "z"vai primeiro do que os valores-z zebra.

ATUALIZAÇÃO 6/6/2016 - Sobre o comentário @jornhd, é realmente uma boa solução, mas ainda é um pouco complexa, portanto, recomendo envolvê-lo em uma classe Extension, como esta:

public static class MyExtensions
{
    public static IOrderedEnumerable<T> NullableOrderBy<T>(this IEnumerable<T> list, Func<T, string> keySelector)
    {
        return list.OrderBy(v => keySelector(v) != null ? 0 : 1).ThenBy(keySelector);
    }
}

E use-o como:

var sortedList = list.NullableOrderBy(f => f.SomeString);
Jaider
fonte
2
Eu acho que isso é mais legível, sem a constante desagradável: .OrderBy (f => f.SomeString! = Null? 0: 1) .ThenBy (f => f.SomeString)
jornhd
14

Eu tenho outra opção nesta situação. Minha lista é objList, e eu tenho que pedir, mas os nulos devem estar no final. minha decisão:

var newList = objList.Where(m=>m.Column != null)
                     .OrderBy(m => m.Column)
                     .Concat(objList.where(m=>m.Column == null));
Gurgen Hovsepyan
fonte
Isso pode funcionar em cenários em que se deseja resultado com outros valores como 0 no lugar de nulo.
Naresh Ravlani # 1/16
Sim. Apenas substitua nulo por 0.
Gurgen Hovsepyan
esta é a única resposta que funcionou para mim, o restante manteve os nulos no início da lista.
BMills 15/08/19
9

Eu estava tentando encontrar uma solução LINQ para isso, mas não consegui resolver isso com as respostas aqui.

Minha resposta final foi:

.OrderByDescending(p => p.LowestPrice.HasValue).ThenBy(p => p.LowestPrice)
user1
fonte
7

minha decisão:

Array = _context.Products.OrderByDescending(p => p.Val ?? float.MinValue)
RTK
fonte
7

Isso é o que eu criei porque estou usando métodos de extensão e também meu item é uma string, portanto não .HasValue:

.OrderBy(f => f.SomeString == null).ThenBy(f => f.SomeString)

Isso funciona com objetos LINQ 2 na memória. Não testei com a EF ou com qualquer DB ORM.

AaronLS
fonte
0

Abaixo está o método de extensão para verificar se é nulo se você deseja classificar na propriedade filho de um keySelector.

public static IOrderedEnumerable<T> NullableOrderBy<T>(this IEnumerable<T> list, Func<T, object> parentKeySelector, Func<T, object> childKeySelector)
{
    return list.OrderBy(v => parentKeySelector(v) != null ? 0 : 1).ThenBy(childKeySelector);
}

E use-o como:

var sortedList = list.NullableOrderBy(x => x.someObject, y => y.someObject?.someProperty);
Manish Patel
fonte
0

Aqui está outra maneira:

//Acsending
case "SUP_APPROVED_IND": qry =
                            qry.OrderBy(r => r.SUP_APPROVED_IND.Trim() == null).
                                    ThenBy(r => r.SUP_APPROVED_IND);

                            break;
//….
//Descending
case "SUP_APPROVED_IND": qry =
                            qry.OrderBy(r => r.SUP_APPROVED_IND.Trim() == null).
                                    ThenByDescending(r => r.SUP_APPROVED_IND); 

                            break;

SUP_APPROVED_IND is char(1) in Oracle db.

Observe que r.SUP_APPROVED_IND.Trim() == nullé tratado comotrim(SUP_APPROVED_IND) is null no Oracle db.

Veja isto para detalhes: Como posso consultar valores nulos na estrutura da entidade?

Leonid Minkov
fonte
0

Outra opção (útil para o nosso cenário):

Temos uma tabela de usuários, armazenando ADName, LastName, FirstName

  • Os usuários devem ser alfabéticos
  • Contas sem nome / sobrenome também, com base em seu nome AD - mas no final da lista de usuários
  • Usuário fictício com o ID "0" ("Sem seleção") deve estar sempre no topo.

Alteramos o esquema da tabela e adicionamos uma coluna "SortIndex", que define alguns grupos de classificação. (Deixamos uma diferença de 5, para inserir grupos mais tarde)

ID | ADName |      First Name | LastName | SortIndex
0    No Selection  null         null     | 0
1    AD\jon        Jon          Doe      | 5
3    AD\Support    null         null     | 10     
4    AD\Accounting null         null     | 10
5    AD\ama        Amanda       Whatever | 5

Agora, em termos de consulta, seria:

SELECT * FROM User order by SortIndex, LastName, FirstName, AdName;

em Expressões de método:

db.User.OrderBy(u => u.SortIndex).ThenBy(u => u.LastName).ThenBy(u => u.FirstName).ThenBy(u => u.AdName).ToList();

que produz o resultado esperado:

ID | ADName |      First Name | LastName | SortIndex
0    No Selection  null         null     | 0
5    AD\ama        Amanda       Whatever | 5
1    AD\jon        Jon          Doe      | 5
4    AD\Accounting null         null     | 10
3    AD\Support    null         null     | 10     
diagnosticar
fonte