sábado, 26 de septiembre de 2015

LINQ. Ordenar por múltiples criterios.

Esta es una consulta bastante habitual.

Cuando tenemos una colección de elementos podemos utilizar los métodos de extensión OrderBy y OrderByDescending para ordenarla de forma sencilla por alguna de sus propiedades pero, ¿qué sucede cuando el criterio de ordenación es múltiple?


Para el ejemplo vamos a suponer que tenemos una variable Personas  que es una lista genérica de instancias de la clase Persona. La cual tiene tres propiedades: Nombre, Apellido y Edad.


class Persona
{
    public string Nombre { get; set; }
    public string Apellido { get; set; }
    public int Edad { get; set; }
}

...

List<Persona> Personas = new List<Persona>();
Class Persona
    Property Nombre As String
    Property Apellido As String
    Property Edad As Integer
End Class

....

Private Personas As New List(Of Persona)

Así podríamos ordenar de forma sencilla la lista Personas por la propiedad Apellido utilizando el método de extensión OrderBy.


IEnumerable<Persona> PersonasOrdenadas = Personas.OrderBy(p => p.Apellido);
Dim PersonasOrdenadas As IEnumerable(Of Persona) = Personas.OrderBy(Function(p) p.Nombre)

La cosa se empieza a complicar si lo que deseamos es ordenar la lista por apellido y que, los que tengan el mismo apellido, se ordenen por edad.

Podríamos utilizar otra sobrecarga del método OrderBy que recibe como parámetro un objeto que implementa la interfaz IComparer<T> que contiene la lógica para ordenar los elementos de la lista.

En primer lugar deberemos crear el comparador para la clase Persona que implementa la interfaz IComparer<Persona> y que definirá la lógica para ordenar los elementos por apellido y edad en el método Compare:


class CompareApellidoEdad : IComparer<Persona>
{
    public int Compare(Persona x, Persona y)
    {
        int ComparaPersona = string.Compare(x.Apellido, y.Apellido);
        if (ComparaPersona == 0)
            ComparaPersona = x.Edad.CompareTo(y.Edad);
        return ComparaPersona;
    }
}
Class CompareApellidoEdad
    Implements IComparer(Of Persona)

    Public Function Compare(x As Persona, y As Persona) As Integer Implements IComparer(Of Persona).Compare
        Dim ComparaPersona As Integer = String.Compare(x.Apellido, y.Apellido)
        If ComparaPersona = 0 Then
            ComparaPersona = x.Edad.CompareTo(y.Edad)
        End If
        Return ComparaPersona
    End Function

End Class

Ahora ya podríamos utilizar la nueva sobrecarga del método OrderBy:


IEnumerable<Persona> PersonasOrdenadas = Personas.OrderBy(p => p, new CompareApellidoEdad());
Dim PersonasOrdenadas As IEnumerable(Of Persona) = Personas.OrderBy(Function(p) p, New CompareApellidoEdad())

Demasiado trabajo para algo tan simple ¿verdad?



Por suerte disponemos de otros dos métodos de extensión que nos permiten realizar esta tarea de una forma mucho más sencilla: ThenBy y ThenByDescending.

Estos métodos de extensión se aplican a objetos que implementan la interfaz IOrderedEnumerable, como sucede con el valor devuelto por los métodos OrderBy y OrderByDescending. Los métodos añaden el criterio de ordenación indicado respetando los existentes en el objeto IOrderedEnumerable y devuelve un nuevo objeto IOrderedEnumerable.

De esta forma resulta tremendamente sencillo realizar la ordenación:


IEnumerable<Persona> PersonasOrdenadas = Personas.OrderBy(p => p.Apellido).ThenBy(p => p.Edad);
Dim PersonasOrdenadas As IEnumerable(Of Persona) = Personas.OrderBy(Function(p) p.Apellido).ThenBy(Function(p) p.Edad)

Dado que, como ya he comentado, los métodos ThenBy y ThenByDescending devuelven también objetos del tipo IOrderedEnumerable, podemos encadenar tantos métodos ThenBy y ThenByDescending como queramos.


IEnumerable<Persona> PersonasOrdenadas = Personas.OrderBy(p => p.Apellido)
    .ThenBy(p => p.Edad)
    .ThenByDescending(p => p.Nombre);
Dim PersonasOrdenadas As IEnumerable(Of Persona) = Personas.OrderBy(Function(p) p.Apellido).ThenBy(Function(p) p.Edad).ThenByDescending(Function(p) p.Nombre)

5 comentarios:

  1. Excelente artículo. Me sirvió esto que no encontraba por ningún lado.

    ResponderEliminar
  2. Tengo enlazado un DataGridView y al pulsar cabecaras ,( DataGridView.Columns(e.ColumnIndex)), ordeno como explicas en los ejemplos:

    Select DataGridView.Columns(e.ColumnIndex).name
    case "Codigo"
    DataGridView.DataSource = ColDatosDeOperarios.OrderBy(Function(x) x.Codigo).ToList
    case "Nombre"
    DataGridView.DataSource = ColDatosDeOperarios.OrderBy(Function(x) x.Nombre).ToList
    etc..
    End Select

    como puedo hacerlo directamente, (sin la select), sustituyendo x.Codigo, x.Nombre

    Gracias y un saludo.

    ResponderEliminar