sábado, 21 de julio de 2012

[Linq] Distinct y GroupBy usando IEquatable<>

 

Introducción


Al hace uso de linq con objetos se presenta un problema ya que no se resuelve de forma correcta la comparaciones entre entidades, esto se debe al hacer uso de un objeto el mismo se compara por medio de la referencia (o sea del puntero en memoria de la instancia de la entidad) y no su contenido

Si se usara tipos simples no se tendría este inconveniente ya que las comparaciones se harían por valor, pero al ser objetos se usa la referencia, es por eso que se será necesario indicar mediante alguna técnica como comparar las entidades.

Para el ejemplo se trabajara con una lista de artículos, declarando una clase del estilo

public class ArticuloBase
{
    public int Id { get; set; }
    public string Nombre { get; set; }

    public int Stock { get; set; }
    public decimal Precio { get; set; }

}

el contenido de las listas tendrá ítems duplicados, aplicando las diferentes técnicas se podría usar linq directamente con los objetos de la lista.

Distinct


Si usamos distinct de linq sobre una lista para evitar duplicados no obtendremos efecto alguno

imagen1

ya que la lista resultante sigue teniendo 4 ítems, cuando debería haber detectado duplicados, para solucionar el problema será necesario definir como es que la entidad debe ser comparada

La primer opción será por medio de un comparador, se define una clase que implemente IEqualityComparer<>

public class ArticuloComparer : IEqualityComparer<ArticuloBase>
{
    public bool Equals(ArticuloBase x, ArticuloBase y)
    {
        return x.Id == y.Id;
    }

    public int GetHashCode(ArticuloBase obj)
    {
        return obj.Id.GetHashCode();
    }
}

esto hace que al usarse como parte del distinct permita conocer que entidades deben ser iguales

imagen2

ahora si se detecto el ítem duplicado y solo se obtiene como resultado 3, es necesario remarcar que en la clase se definio que compara por id, por lo que un diferente stock será descartado.

Existe otra opción valida cuando se usan entidades la cual consiste en implementar la interfaz IEquatable<>, en este caso se definió una clase heredada para separa la implementación y poder testear, pero se podría haber unido todo en una única clase

public class Articulo : IEquatable<Articulo>
{
    public int Id { get; set; }
    public string Nombre { get; set; }

    public int Stock { get; set; }
    public decimal Precio { get; set; }


    #region IEquatable<Articulo> Members

    public bool Equals(Articulo other)
    {
        if (Object.ReferenceEquals(other, null)) return false;

        if (Object.ReferenceEquals(this, other)) return true;

        return this.Id.Equals(other.Id);
    }


    public override int GetHashCode()
    {
        int hashDescription = this.Id == null ? 0 : this.Id.GetHashCode();

        return hashDescription;
    }

    #endregion

}

con esta otra forma se obtiene el mismo resultado

imagen3

 

Group by


Al usar el group by de linq se presentan los mismo problemas que el distinct, la entidad no puede resolver como debe agrupar cada entidad

imagen4

Pero si lo indicamos por medio del IEquatable<>, en ese caso podra reconocer cual, o cuales, propiedades una entidad es igual a otra

imagen5

El articulo 2 ahora si se agrupa y suma su stock

Código


[C#]
 

1 comentario: