miércoles, 13 de octubre de 2010

Distinct en linq

He de reconocer que linq me gusta cada día más. Y en especial cuando te facilita la tarea que en sql sería, para mí, un dolor de cabeza de los buenos.
Heme aquí con el siguiente problema. Tengo un tabla con calles que debo de visualizarla sin duplicados. A lo cual, sin problema ninguno me digo: “utiliza un distinct”. Pero los problemas se empiezan a mostrar cuando necesito que el distinct me lo haga por dos campos diferentes: el nombre y el tipo de calle. Y, además, que solamente me haga el distinct en el caso de que la población sea Madrid. (no es broma, es un caso real).
¿Cómo carajo hago esto con SQL? Me empezó a girar la cabeza con agrupaciones, uniones y demás técnicas que, he de reconocer, no llego a entender.
Pues en Linq no es algo evidente, pero una vez realizado es la técnica más potente para realizar eliminaciones de duplicados, que he visto hasta ahora.
/// <summary>
/// Retorna un listado de calles evitando los duplicados (usando un comparador).
/// </summary>
/// <param name="textoBusqueda"></param>
/// <returns></returns>
public static IEnumerable<calles> GetCallesList()
{
// Llamo a mi modelo de datos (DataContext) y me traigo el listao de calles.
IQueryable<calles> listaDeCalles = from calle in DataContext.Calles_SQL
select calle;
// Realizo un Distinct complejo con una clase que herede IEqualityComparers<T>
IEnumerable<calles> callesSinDuplicados = listaDeCalles.Distinct(callesComparer());
return callesSinDuplicados;
}

El truco está en utilizar la una clase propia para utilizarla como filtro del distinct. Y para ello debe heredar de la interface IEqualityComparers<T>

/// <summary>
/// Clase de comparación para el Distintc del listado de Calles
/// </summary>
public class callesComparer : IEqualityComparer<calles>
{
public bool Equals(calles x, calles y)
{
//Reviso si los objetos son la misma referencia.
if (Object.ReferenceEquals(x, y)) return true;

//Reviso si alguno de los objetos es null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;

//Y aquí está la magia y la tremenda potencia de esta comparación. 
//Puedo poner todas las condiciones que quiera. 
//Por ejemplo que el nombre y el tipo de vía sean iguales. 
//Y que además en el campo tabla tenga el valor M. Imaginaros la potencia de esto.
return x.nombre == y.nombre && x.tipovia == y.tipovia && x.tabla == "M";
}

       //La otra cosa importante es que debe construir un código hash para hacer la comparación 
//entre los registros que va a comparar y para eso hay que elegir cuidadosamente los campos 
//que vamos a utilizar. 
//Es una mala práctica hacer obj.ToString().GetHashCode(). 
       public int GetHashCode(calles calle)
{
//Compruebo si el objeto es null
if (Object.ReferenceEquals(calle, null)) return 0;


           //Utilizo el nombre y el tipo de via para construir el HasCode. 
int hashCalleNombre = calle.nombre == null ? 0 : calle.nombre.GetHashCode();
int hashCalleTipoVia = calle.tipovia.GetHashCode();

//Devuelvo la unión de ambos código hash.
return hashCalleNombre ^ hashCalleTipoVia;
}
}

Espero que os sea tan útil como me lo es para mí… Sonrisa

P.D. Continuación de esta entrada en Distinct en linq. Con IQueryables.

1 comentario:

kikorb dijo...

No dejes tus post. Acabo de conocer tu blog, de darle una vuelta y es muy interesante lo que vas poniendo.

Animo y al toro :)