24 oct 2011

DataTable Distinct

Tenemos un DataTable con una moderada cantidad de filas, de éstas nos interesan ciertas columnas y que cuyos valores no se repitan: Necesitamos un Distinct. Usando C# ¿Cómo logramos eso?

De momento se me ocurren dos soluciones: LINQ y una técnica usa el  DefaultView  . Sería cuestión de probar cual es más eficiente ya que las dos logran el mismo resultado. Hagamos entonces las prueba.

Primero nos hacemos un proyecto consola y nos creamos un DataTable con unas tres columnas; lo llenamos con valores aleatorios para simular las filas. Esta prueba la realizaremos con 200 mil registros:

//Para llevar el control del tiempo de ejecucion
Stopwatch st = new Stopwatch();

//Creamos la tabla
DataTable dt = new DataTable();
dt.Columns.Add("Codigo", typeof(int));
dt.Columns.Add("Descripcion", typeof(string));
dt.Columns.Add("Ubicacion", typeof(int));

//La llenamos con valores aleatorios
Random x = new Random();
for (decimal i=0; i < 200000; i++)
{
    int X = x.Next(1,100);
    int Y = x.Next(1,10);
    int Z = x.Next(1,1000);
    dt.Rows.Add(X, Y.ToString(), Z);

}

De esta tabla voy a querer las columnas Codigo y Descripcion pero sin registros repetidos. Completamos esta tarea aplicando el distinct según LINQ,

st.Start();
//Linq para extraer las columnas deseadas sin que se repitan
var distinctRows = (from DataRow dRow in dt.Rows
                    select new
                    {
                        col_id = dRow["Codigo"],
                        col_desc = dRow["Descripcion"]
                    }).Distinct();
st.Stop();

//Mostramos la cantidad de registros únicos encontrados y el tiempo de ejecución
string tiempoCarga = (st.ElapsedMilliseconds / 1000).ToString();
Console.WriteLine("Registros {0} cargados en {1} segundos unsando LinQ.",distinctRows.Count().ToString(), tiempoCarga);

Ahora, podemos obtener el mismo resultado  usando el DefaultView del DataTable y vaciandolo en un nuevo DataTable:
st.Reset();//Reseteamos el contador de tiempo
st.Start();
DataTable dt2 = dt.DefaultView.ToTable(true, "Codigo", "Descripcion");
st.Stop();

//Mostramos la cantidad de registros únicos y el tiempo de ejecución
tiempoCarga = (st.ElapsedMilliseconds / 1000).ToString();
Console.WriteLine("Registros {0} cargados en {1} segundos unsando DefaultView.ToTable.",dt2.Rows.Count.ToString(), tiempoCarga);

Console.ReadLine();

Ahora procedemos a ejecutar la prueba y obtener los resultados:

Como podemos observar ambos llegan a la misma cantidad de registros únicos, pero los tiempos de respuesta difieren significativamente uno de otro. Mientras el Distinct de LINQ es prácticamente instantáneo, usando la técnica del  DefaultView consume en promedio 6 segundos. Ganador absoluto de la prueba LINQ!

Solo como referencia final, la forma en la que se recorren los registros obtenidos por medio del LINQ es la siguiente:

double count = 0;
foreach (var row in distinctRows)
{
    count++;
    Console.WriteLine("{0} - ID:{0}  Descripcion {1} ", count.ToString(), row.col_id.ToString(), row.col_desc.ToString());
}

Interesante!

Roy {aka. Foy}

Autor & Editor

Desarrallador y líder técnico, con experiencia en tecnologías Microsoft desde los tiempos del VB6 y el asp clásico hasta el .Net Core, pasando por COM+, javascript, angularjs, Ionic, xaml, cordova, MVC, Web Api, Sql Server, Oracle... . Ávido lector, apasionado programador.