Introducción:
Cuando Visual Basic 9.0 (ó 2008) crea un tipo anónimo en una consulta de LINQ (siempre crea un
tipo anónimo para el valor devuelto por la cláusula Select), se ve que las propiedades las
clasifica por orden alfabético, pero en el orden de los nombres de esas propiedades.
Nota:
Este artículo lo escribo a raíz de una consulta de erickorlando en mis foros.
Si quieres ver ese hilo, pulsa en este link:
http://foros.elguille.info/Mensajes.aspx?ID=39024
Esto en principio no es problema, pero cuando el resultado de esa consulta se asigna a un
DataGridView, ese control crea automáticamente las columnas (cabeceras) a partir de las columnas
de los datos que se le asigna, y en el caso de asignarle el resultado de una consulta LINQ, esas
columnas no son otra cosa que las propiedades del tipo anónimo creado en la orden Select. Y
debido a que VB ordena las propiedades/columnas por el nombre de las mismas, resulta que es
posible que lo que se muestre en el DataGridView no sea lo deseado.
Por ejemplo, si tenemos una consulta como la de este código:
' Listado 1
Dim cust = From c In northwind.Customers _
Where c.CompanyName.StartsWith("L") _
Order By c.CustomerID, c.CompanyName, c.ContactName _
Select c.CustomerID, c.CompanyName, c.ContactName
' En DataGridView se clasifica como CompanyName, ContactName, CustomerID
DataGridView1.DataSource = cust
El DataGridView genera las columnas tal como se ve en la figura 1.
Fíjate que en vez de mostrarse tal como se indica en el Select, se ha clasificado por el nombre
de las propiedades.
Figura 1. Las columnas del grid están por orden alfabético
La solución que proporciona erickorlando en el hilo que te he comentado antes, es creando un
tipo anónimo que tenga los nombres de las columnas de forma que se clasifiquen bien y se
muestren los datos como se quiere... es decir, primero el ID del cliente, después la empresa y
por último el nombre del contacto.
Este es el código que hace eso:
' Listado 2
Dim cust = From c In northwind.Customers _
Where c.CompanyName.StartsWith("L") _
Order By c.CustomerID, c.CompanyName, c.ContactName _
Select New With {Key .Cliente = c.CustomerID, _
Key .Compañia = c.CompanyName, _
Key .Contacto = c.ContactName}
DataGridView1.DataSource = cust
Ese mismo código se puede escribir también de esta forma:
' Listado 3
Dim cust = From c In northwind.Customers _
Where c.CompanyName.StartsWith("L") _
Order By c.CustomerID, c.CompanyName, c.ContactName _
Select Cliente = c.CustomerID, _
Compañia = c.CompanyName, _
Contacto = c.ContactName
DataGridView1.DataSource = cust
La diferencia entre el listado 2 y el 3, es que en el segundo las propiedades son de solo
lectura (Key indica que es de solo lectura) y en el listado 3, son de lectura y escritura,
además de que, para generar el tipo anónimo no es necesario indicarlo explícitamente. Es la
ventaja de VB, je, je, que hasta en esto nos facilita el trabajo.
Los dos listados anteriores mostrarán los datos como en la figura 2.
Figura 2. Las columnas ordenadas como queremos
El problema de esta solución es que si esos nombres de las propiedades indicadas después de
Select no están en el orden que nos gustaría, pues... que no se mostrarían como queremos.
Por tanto, la solución que "casi" nos puede solucionar la papeleta es decirle al DataGridView
que no genere automáticamente los nombres, de las columnas.
Pero si le decimos eso... pues... ¡no veremos nada! (o casi), así que... tenemos que crear las
columnas manualmente, indicar qué queremos que se muestre en la cabecera del DataGridView...
pero lo más importante es que le tenemos que indicar qué columna de los datos corresponde con
cada columna del DataGridView.
Los nombres de las columnas de los datos son los nombres de las propiedades del tipo anónimo que
creamos en la consulta de LINQ, para ser más concreto los nombres que usemos en el Select.
Este es el código que no genera los nombres de las columnas del DataGridView y que puedes
usar para que estén en el orden que te interese:
' Listado 4
Dim cust = From c In northwind.Customers _
Where c.CompanyName.StartsWith("L") _
Order By c.CustomerID, c.CompanyName, c.ContactName _
Select c.CustomerID, c.CompanyName, c.ContactName
DataGridView1.AutoGenerateColumns = False
DataGridView1.Columns.Clear()
DataGridView1.Columns.Add("CustomerID", "CustomerID")
DataGridView1.Columns.Add("CompanyName", "CompanyName")
DataGridView1.Columns.Add("ContactName", "ContactName")
DataGridView1.Columns(0).DataPropertyName = "CustomerID"
DataGridView1.Columns(1).DataPropertyName = "CompanyName"
DataGridView1.Columns(2).DataPropertyName = "ContactName"
DataGridView1.DataSource = cust
Fíjate que lo que hay en la cláusula Select es lo mismo que en el listado 1, es decir, los
nombres de las columnas de la tabla Customers de la base de datos Northwind.
Esos nombres de las columnas de la tabla son los que el compilador de Visual Basic creará
para los nombres de las propiedades del tipo anónimo que se crea siempre con Select. Pero como
ya vimos que el VB ordena alfabéticamente esas propiedades, lo que tenemos que hacer es asignar
al DataGridView lo que queremos que se muestre, y en el orden que nos interese. En este caso,
las columnas se muestran en el mismo orden que están indicadas después de Select, y el resultado
sería el de la figura 3.
Figura 3. Las columnas ordenadas manualmente
El código del listado 3, hace lo siguiente:
- Genera el código de la consulta de LINQ.
- Le asignamos un valor False a la propiedad AutoGenerateColumns del DataGridView para
que no genere las columnas.
- Eliminamos las columnas que tuviera, ya que si antes hemos mostrado los datos, pues
habría más columnas de la cuenta, tal como puedes ver en la figura 4.
Figura 4. Si no borramos las columnas, se van acumulando
- Añadimos cada una de las columnas que queremos tener.
El primero parámetro del método Add es el nombre de la columna, y el segundo es lo que
queremos que se muestre en esa columna. Si quisiéramos que se mostrara como en la figura 5,
tendríamos que crear esas columnas tal como se muestra en el listado 5.
Figura 5. Cabeceras personalizadas
' Listado 5
DataGridView1.Columns.Add("CustomerID", "ID")
DataGridView1.Columns.Add("CompanyName", "Empresa")
DataGridView1.Columns.Add("ContactName", "Contacto")
- Como te decía antes, la parte más importante es asignar a la propiedad
DataPropertyName de cada columna el campo al que queremos enlazar los datos, ya que ese
es el "dato" que tiene en cuenta el DataGridView para saber qué se debe mostrar en cada
columna de cada fila.
Nota:
Si el código del listado 4 lo pones junto con otros códigos que manipulen el contenido
del DataGridView, debes borrar siempre el contenido de las columnas:
DataGridView1.Columns.Clear() antes de asignar los datos al DataGridView por medio de la
asignación a la propiedad DataSource. Al menos si no quieres que haya columnas huérfanas, al
estilo de la figura 4.
Y si siempre vas a usar esas columnas con el DataGridView, puedes escribir todo el código que
hay después de crear la consulta LINQ y antes de asignar el valor al DataSource en el evento
Load del formulario o en cualquier otro sitio, ya que si siempre vas a asignar los mismos datos
(las mismas columnas/propiedades), pues... te evitas estar repitiendo ese código.
Tal como te comento, todo este "follón" solo es necesario hacerlo con Visual Basic, ya que en
C# no se ordenan las propiedades de los tipos anónimos generados en la cláusula select.
En el siguiente código tienes lo que habría que escribir en C# para tener algo parecido a la
figura 3.
// Listado 6
var cust = from c in northwind.Customers
where c.CompanyName.StartsWith("L")
orderby c.CustomerID, c.CompanyName, c.ContactName
select new {c.CustomerID, c.CompanyName, c.ContactName};
DataGridView1.DataSource = cust;
En el ZIP tienes dos proyectos, uno para Visual Basic y el otro para C#.
En esos proyectos se usan objetos LINQ to SQL, que están conectados a la base de datos
Northwind, por tanto, debes tener esa base de datos y seguramente tendrás que cambiar la
cadena de conexión, ya que la que yo uso es:
Data Source=(local)\sqlexpress; Initial Catalog=Northwind; Integrated Security=True.
Nos vemos.
Guillermo
P.S.
Si quieres saber más sobre todo lo referente a LINQ y otras novedades de Visual Basic 9.0 (o
Visual Basic
2008), te recomiendo que compres mi libro electrónico
Las Novedades de Visual Basic 9.0. ;-))))
Este
artículo también está publicado en La Cueva de Desarrollo de Solid Quality Mentors.
Espacios de nombres usados en el código de este artículo:
System.Windows.Forms
System.Linq