jueves, 15 de julio de 2010

[Linq] Join DataTable


Introducción


Si bien el objeto DataSet es muy completo y brinda una funcionalidad muy amplia hay que reconocer que ciertas operación con la información pueden estar fuera del alcance del mismo, por lo tanto se debe recurrir a otras técnicas.

Es bien conocido que el DataSet posee un método de nombre Merge(), el cual resulta muy práctico para unir información proveniente de diversas consultas, siempre y cuando se respete la misma estructura en los datos, pero este solo une información, no permite obtener obtener diferencias entre las dos fuentes de datos.

Que sucede si es necesario cruzar registros para obtener diferencias entre ellos y trabajar con esta información, es aquí donde entra en juego Linq, proveyendo una herramienta muy amplia de posibilidades.

 

Join entre DataSet, devolviendo la lista de string


En el ejemplo se devolverá una lista de string con el resultado del join de ambos datatable. Una de las listas representan una relojes de registro de ingreso de personal, mientras que la otra devuelve cuales de estoy hay que controlar.

[C#]

private void btnListaString_Click(object sender, EventArgs e)
{
    #region Defino Columnas DataGridView
    dataGridView1.Columns.Clear();

    DataGridViewColumn col = new DataGridViewColumn(new DataGridViewTextBoxCell());
    col.HeaderText = "Relojes ha Controlar";
    col.Name = "Relojes";
    col.DataPropertyName = "Value";
    col.Width = 160;
    dataGridView1.Columns.Add(col);

    #endregion


    List<string> lista = (from dt1 in ObtenerListadoRelojes().AsEnumerable()
                          join dt2 in ObtenerRelojesControlar().AsEnumerable() on dt1.Field<int>("Numero") equals dt2.Field<int>("NumControl")
                          select dt1.Field<string>("Nombre")).ToList();


    dataGridView1.AutoGenerateColumns = false;
    dataGridView1.DataSource = lista.Select(x => new { Value = x }).ToList(); 
}

[VB.NET]

Private Sub btnListaString_Click(ByVal sender As Object, ByVal e As EventArgs)

    'Defino Columnas DataGridView
    dataGridView1.Columns.Clear()

    Dim col As New DataGridViewColumn(New DataGridViewTextBoxCell())
    col.HeaderText = "Relojes ha Controlar"
    col.Name = "Relojes"
    col.DataPropertyName = "Value"
    col.Width = 160
    dataGridView1.Columns.Add(col)


    Dim lista As List(Of String) = (From dt1 In ObtenerListadoRelojes().AsEnumerable() _
                                     Join dt2 In ObtenerRelojesControlar().AsEnumerable() _
                                        On dt1.Field(Of Integer)("Numero") Equals dt2.Field(Of Integer)("NumControl") _
                                     Select dt1.Field(Of String)("Nombre")).ToList()


    dataGridView1.AutoGenerateColumns = False

    dataGridView1.DataSource = lista.[Select](Function(x) New With {.Value = x}).ToList()

End Sub


Es interesante marcar como el “join” de linq, en la parte de “on” se asemeja a una consulta SQL, igualando que campo se comparan en la operación.

En la sección del “select” de la consulta, solo se define un campo el tipo string, el cual es utilizado para conformar cada ítem de la lista devuelta por la consulta linq.


Join entre DataSet - mapeando con una clase


El uso de clases para convertir datos en una estructura diferente puede resultar útil cuando se requiere obtener datos complejos con los cuales trabajar.

En este ejemplo era necesario obtener dos atributos provenientes de los datos originales, usar un string era imposible ya que este solo representa un dato, por lo tanto el uso de la clase permitió lograr el objetivo.

[C#]

private void btnlistaClase_Click(object sender, EventArgs e)
{
   
    #region Defino Columnas DataGridView
    dataGridView1.Columns.Clear();

    DataGridViewColumn col1 = new DataGridViewColumn(new DataGridViewTextBoxCell());
    col1.HeaderText = "Relojes ha Controlar";
    col1.Name = "Relojes";
    col1.DataPropertyName = "Nombre";
    col1.Width = 160;
    dataGridView1.Columns.Add(col1);

    DataGridViewColumn col2 = new DataGridViewColumn(new DataGridViewTextBoxCell());
    col2.HeaderText = "Horario";
    col2.Name = "Horario";
    col2.DataPropertyName = "Hora";
    col2.Width = 50;
    dataGridView1.Columns.Add(col2);

    #endregion

    List<Datos> lista = (from dt1 in ObtenerListadoRelojesConHorarios().AsEnumerable()
                         join dt2 in ObtenerRelojesControlar().AsEnumerable() on dt1.Field<int>("Numero") equals dt2.Field<int>("NumControl")
                         select new Datos
                         {
                             Nombre = dt1.Field<string>("Nombre"),
                             Hora = dt1.Field<string>("Hora")
                         }).ToList();


    dataGridView1.AutoGenerateColumns = false;
    dataGridView1.DataSource = lista;
}

[VB.NET]

Private Sub btnlistaClase_Click(sender As Object, e As EventArgs)

       'Defino Columnas DataGridView
	dataGridView1.Columns.Clear()

	Dim col1 As New DataGridViewColumn(New DataGridViewTextBoxCell())
	col1.HeaderText = "Relojes ha Controlar"
	col1.Name = "Relojes"
	col1.DataPropertyName = "Nombre"
	col1.Width = 160
	dataGridView1.Columns.Add(col1)

	Dim col2 As New DataGridViewColumn(New DataGridViewTextBoxCell())
	col2.HeaderText = "Horario"
	col2.Name = "Horario"
	col2.DataPropertyName = "Hora"
	col2.Width = 50
	dataGridView1.Columns.Add(col2)


       Dim lista As List(Of Datos) = (From dt1 In ObtenerListadoRelojesConHorarios().AsEnumerable() _
                                        Join dt2 In ObtenerRelojesControlar().AsEnumerable() _
                                           On dt1.Field(Of Integer)("Numero") Equals dt2.Field(Of Integer)("NumControl") _
                                        Select New Datos() With { _
                                               .Nombre = dt1.Field(Of String)("Nombre"), _
                                               .Hora = dt1.Field(Of String)("Hora") _
                                           }).ToList()


	dataGridView1.AutoGenerateColumns = False
       dataGridView1.DataSource = lista

End Sub


Hay que remarcar como el la sección del “select” se crea un nueva instancia de la clase por cada ítem resultante de la query, en esta es donde se especifica como mapean las propiedades con los campos originales.

 

Ejemplos de código


El código ha sido desarrollado usando Visual Studio 2008

 

[C#] 
[VB.NET] 

7 comentarios:

  1. Leandro buenos dias, estuve viendo tu articulo y tengo una pregunta...
    tengo dos orígenes de datos los cuales me retornan columnas variables pues se genera por medio de un pivot de acuerdo al rango de fechas, si el rango es de 2 meses me genera dos columnas, y asi... la columna comun es el id, la cual usaria para hacer la union. los dos datatable tienen el mismo numero de columnas.
    Como puedo hacer el union de los 2 datatable con todas sus columnas..
    gracias

    ResponderEliminar
  2. hola ricardo.cabra

    lo unico que se me ocurre es normalizar, o sea definir un numero de columnas fijas para el resultado

    si uno devuelve 2 columnas y otro 5 columnas, entonces el resultado seran 5 columnas, solo que del primer datatable se cargaran solo las 2 primeras dejando el resto con un valor por defecto o sino nulas, del segundo se cargan todas las columnas

    saludos

    ResponderEliminar
  3. hola, soy algo nueva en esto y quiero saber si puedo concatenar 2 grids ya sea con el metodo merge o con el join datatable los dos datatables obtinen su origen de datos de un datasource con una consulta a la base de datos en oracle.
    esto lo requiero para emitir un reporte el cual me traiga 3 columnas, 2 de ellas de un grid maestro y la tercera de su respectivo grid detalle

    ResponderEliminar
  4. hola magugo

    no entendi muy bien el planteo que la pregunta, mas que nada porque no quedo claro de dodne surge la informacion
    mencionas los DataSource de los grid pero esto que tiene que ver luego con los reportes

    o sea si realzias consultas a Oracle y cargas datatable que tienen la misma estructura de campos, podrias luego usa el Merge() para unir los registros

    saludos

    ResponderEliminar
  5. LEANDRO TENGO UNA CONSULTA, DE PASAR UN QUERY SQL A LINQ-DATATABLE:
    SELECT TH.cNombre, COUNT(TH.cNombre) cant, H.cNombre
    FROM RESERVA.Habitacion H
    INNER JOIN RESERVA.TipoHabitacion TH ON H.iIdTipoHabitacion = TH.IdTipoHabitacion
    WHERE th.IdTipoHabitacion <> 1
    GROUP BY TH.cNombre, H.cNombre
    HAVING COUNT(TH.cNombre) > 1 OR TH.cNombre = TH.cNombre , Y LLEGUE A PASARLO DE ESA FORMA: Dim tblQuery = From r In tblHabitTarifa.AsEnumerable() _
    Group r By idtipohabit = r.Field(Of String)("idtipohabitacion1"), _
    tipohabit = r.Field(Of String)("tipohabitacion1"), _
    habit = r.Field(Of String)("habitacion")
    Into cont = Count() _
    Where cont > 1 Or tipohabit = tipohabit And idtipohabit <> 1
    Select idtipohabit, tipohabit, cont , Y NO LOGRO A HACER QUE SOLO CUENTE EL CAMPO: " r.Field(Of String)("tipohabitacion1") "... HELP PLEASE..

    ResponderEliminar
  6. hola fer007

    has analizado ejemplos como estos

    LINQ To SQL Samples - GROUP BY/HAVING

    porque alli veras que para hacer un group by de multiples campos necesitas algo como esto

    Group By Key = New With {p.CategoryID, p.SupplierID} Into Group

    o sea armar un objeto anonimo, que no veo que alli estes definiendo

    ---
    al igual que esto
    Into cont = Count()

    deberia ser
    Where Group.Count() >= 10

    o sea trabajas directo con el Group

    saludos

    ResponderEliminar
  7. HOLLE HERMNO ECHAME LA MANO

    Tengo un dataTable de dos origenes de datos (claves y propiedades), estoy juntando las dos en una sola
    las propiedades están en rangos de filas,quiero repartir cada clave de la table claves (esta tiene solo una columna llamada claves), en cada uno de los rangos mi tabla propiedades le quiero asignar una sola clave y asi sucesivamente con los demás rangos de tal forma que en la DataTable nueva llegue cada rango de filas con una sola clave y así repartidlas, esto conyeva a ser adacentes las filas de dos tables


    ResponderEliminar