Orden de LINQ por columna nula donde el orden es ascendente y los nulos deben ser los últimos

141

Estoy tratando de ordenar una lista de productos por su precio.

El conjunto de resultados debe enumerar los productos por precio de menor a mayor en la columna LowestPrice. Sin embargo, esta columna es anulable.

Puedo ordenar la lista en orden descendente de la siguiente manera:

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

Sin embargo, no puedo entender cómo ordenar esto en orden ascendente.

// i'd like: 100, 101, 102, null, null
sf.
fuente
11
orderby p.LowestPrice ?? Int.MaxValue;Es una manera simple.
PostMan
3
@PostMan: Sí, es simple, logra el resultado correcto, pero OrderByDescending, ThenByes más claro.
Jason
@Jason, sí, no conocía la sintaxis para el orderby, y
fui

Respuestas:

161

Intente poner ambas columnas en el mismo orden.

orderby p.LowestPrice.HasValue descending, p.LowestPrice

De lo contrario, cada pedido es una operación separada en la colección que lo reordena cada vez.

Esto debería ordenar los que tienen un valor primero, "luego" el orden del valor.

DaveShaw
fuente
22
Error común, la gente hace lo mismo con Lamda Syntax: usando .OrderBy dos veces en lugar de .ThenBy.
DaveShaw
1
Esto funcionó para ordenar campos con valores en los campos superiores y nulos en la parte inferior. Usé esto: orderby p.LowestPrice == null, p.LowestPrice ascending Hope ayuda a alguien.
shaijut
@DaveShaw gracias por el consejo - especialmente el comentario - muy ordenado - me encanta
Demetris Leptos
86

Realmente ayuda a comprender la sintaxis de consulta LINQ y cómo se traduce a las llamadas al método LINQ.

Resulta que

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

será traducido por el compilador a

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

Esto no es enfáticamente lo que quieres. Este tipo de Product.LowestPrice.HasValueen descendingorden y luego reordena toda la colección Product.LowestPriceen descendingorden.

Lo que quieres es

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

que puede obtener utilizando la sintaxis de consulta por

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

Para obtener detalles de las traducciones de la sintaxis de consulta a las llamadas a métodos, consulte la especificación del idioma. Seriamente. Léelo

jason
fuente
44
+1 o simplemente ... no escriba la sintaxis de consulta LINQ :) Buena explicación, no obstante
sehe
18

La solución para los valores de cadena es realmente extraña:

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

La única razón por la que funciona es porque la primera expresión OrderBy(), ordena los boolvalores: true/ false. falseresultado vaya primero seguido por el trueresultado (nulables) y ThenBy()clasifique los valores no nulos alfabéticamente.

Entonces, prefiero hacer algo más legible como este:

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

Si SomeStringes nulo, será reemplazado por "z"y luego ordenará todo alfabéticamente.

NOTA: Esta no es una solución definitiva ya que "z"va primero que los valores z como zebra.

ACTUALIZACIÓN 9/6/2016 - Sobre el comentario de @jornhd, es realmente una buena solución, pero aún así es un poco complejo, por lo que recomendaré envolverlo en una clase de Extensión, 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);
    }
}

Y simple usarlo como:

var sortedList = list.NullableOrderBy(f => f.SomeString);
Jaider
fuente
2
Creo que esto es más legible, sin la constante desagradable: .OrderBy (f => f.SomeString! = Null? 0: 1) .ThenBy (f => f.SomeString)
jornhd
14

Tengo otra opción en esta situación. Mi lista es objList, y tengo que ordenar pero los valores nulos deben estar al final. mi decisión:

var newList = objList.Where(m=>m.Column != null)
                     .OrderBy(m => m.Column)
                     .Concat(objList.where(m=>m.Column == null));
Gurgen Hovsepyan
fuente
Esto puede funcionar en escenarios donde uno quiere resultados con otros valores como 0 en lugar de nulo.
Naresh Ravlani
Si. Simplemente reemplace nulo a 0.
Gurgen Hovsepyan
Esta es la única respuesta que funcionó para mí, el resto mantuvo los valores nulos al comienzo de la lista.
BMills
9

Estaba tratando de encontrar una solución LINQ para esto, pero no pude encontrar las respuestas aquí.

Mi respuesta final fue:

.OrderByDescending(p => p.LowestPrice.HasValue).ThenBy(p => p.LowestPrice)
usuario1
fuente
7

mi decisión:

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

Esto es lo que se me ocurrió porque estoy usando métodos de extensión y también mi artículo es una cadena, por lo tanto no .HasValue:

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

Esto funciona con objetos LINQ 2 en la memoria. No lo probé con EF ni con ningún DB ORM.

AaronLS
fuente
0

A continuación se muestra el método de extensión para verificar si es nulo si desea ordenar la propiedad secundaria de un 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);
}

Y simple usarlo como:

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

Aquí hay otra manera:

//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.

Tenga en cuenta que r.SUP_APPROVED_IND.Trim() == nullse trata como trim(SUP_APPROVED_IND) is nullen Oracle db.

Vea esto para más detalles: ¿Cómo puedo consultar valores nulos en el marco de la entidad?

Leonid Minkov
fuente
0

Otra opción (fue útil para nuestro escenario):

Tenemos una tabla de usuario, almacenando ADName, LastName, FirstName

  • Los usuarios deben ser alfabéticos
  • Cuentas sin nombre / apellido también, en función de su nombre AD, pero al final de la lista de usuarios
  • Usuario ficticio con ID "0" ("Sin selección") Debe estar siempre en primer lugar.

Modificamos el esquema de la tabla y agregamos una columna "SortIndex", que define algunos grupos de clasificación. (Dejamos un espacio de 5, para que podamos insertar grupos más 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

Ahora, en cuanto a consultas, sería:

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

en Expresiones de métodos:

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

que produce el 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     
dognose
fuente