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]

6 comentarios:

ricardo.cabra dijo...

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

Leandro Tuttini dijo...

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

magugo dijo...

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

Leandro Tuttini dijo...

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

fer007 dijo...

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..

Leandro Tuttini dijo...

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