martes, 13 de septiembre de 2011

Linq To DataSet: Rompiendo el hielo

En mi oficina trabajamos mucho, quizás en exceso, con el objeto DataTable.

Ahora queremos ir introduciendo poco a poco Linq en nuestras aplicaciones y resulta que siempre que buscas ejemplos de código de Linq, son de Linq To Sql o Entity Framework. Esto está muy bien y seguro que llegará el día en que trabajaré con un ORM como Entity Framework y utilice Linq To Entities para su consulta y manipulación, pero hasta ese día tengo que vivir con cientos de DataTables y además quiero utilizar Linq.

Los chicos de Microsoft han pensado en todo y tenemos disponible un proveedor de Linq denominado Linq To DataSet que nos permite consultar nuestro DataTables a través de Linq.

Si quieres trabajar con este proveedor, lo primero que tienes que hacer es agregar una referencia al ensamblado System.Data.DataSetExtensions.

Como ya sabrás, cualquier origen de datos que quiera trabajar con Linq tiene que implementar IEnumerable(Of T) o IQueryable. Siendo así, tenemos disponible el método DataTable.AsEnumerable que convierte un DataTable en un objeto del tipo Data.EnumerableRowCollection(Of Data.DataRow) que implementa IEnumerable(Of DataRow). Este método lo encontrarás en cualquier consulta de Linq To DataSet y es paso obligado si quieres utilizar Linq To DataSet.

Una vez que ya podemos consultar nuestro DataTable con Linq, es preciso contar que Linq To DataSet permite consultar tanto DataSets no tipados como DataSets tipados. Yo personalmente, no trabajo con DataSets tipados porque no me gustan, pero en el caso de Linq To DataSet, tienen de bueno que se conoce de antemano el nombre y tipo de sus columnas, y de ese modo Linq puede en tiempo de compilación ofrecernos Intellisense, validación de tipos, etc y además no es necesario utilizar el método Field (que veremos más adelante). En cualquier caso, este post está dirigido a DataSets no tipados.

Para los ejemplos de este post, asumiremos que tenemos un DataTable llamado customers con las columnas CustomerID, CompanyName, CompanyTaxCode y CustomerType.

Nuestra primeras consulta serán sencilla y servirán para conocer tipos:

Dim q As Data.EnumerableRowCollection(Of Data.DataRow)

q = From r In customers.AsEnumerable Select r


Que sería equivalente a:

Dim q = From r In customers.AsEnumerable


Si ahora hacemos esta otra consulta, podemos ver como el tipo de destino será Data.EnumerableRowCollection(Of Object) y esto es porque hemos utilizado la propiedad nativa Item del objeto DataRow que devuelve un Object.

Dim q = From r In customers.AsEnumerable Select r.Item("CustomerID")


También podríamos convertir la columna al tipo adecuado:

Dim q = From r In customers.AsEnumerable Select Convert.ToString(r.Item("CustomerID"))


Esta conversión podría parecerte una buena opción, pero te comento que simplemente la hemos visto para ver que puede dar errores y cómo NO deben de hacerse las cosas.

Imagina ahora que el campo CompanyTaxCode (String) y CustomerType (Integer) admites nulos y que tenemos una consulta como la siguiente:

Dim q = From r In customers.AsEnumerable
Where r.Item("CompanyTaxCode") = "13245"
Select Convert.ToString(r.Item("CompanyTaxCode"))

 

clip_image002[8]

Como puedes ver, la propiedad Item (la de toda la vida del objeto DataRow) no se lleva bien con los nulos y además este error lo veremos cuando se ejecute la consulta (en el ejemplo forzada con el método ToList).  Para solucionarlo deberíamos escribir algo como esto:

 

        Dim q =

            From r In customers.AsEnumerable

            Where Not r.IsNull("CompanyTaxCode") AndAlso r.Item("CompanyTaxCode") = "13245"

            Select CompanyTaxCode = Convert.ToString(r.Item("CompanyTaxCode"))

 

Una vez resuelta la comparación con valores nulos, vemos que la conversión explícita de la lista de selección no parece dar errores con los nulos, pero si en vez de CompanyTaxCode (que es del tipo String), seleccionamos CustomerType (que es Integer):

 

        Dim q =

            From r In customers.AsEnumerable

            Select

            CompanyTaxCode = Convert.ToString(r.Item("CompanyTaxCode")),

            CustomerType = Convert.ToInt32(r.Item("CustomerType"))

 

clip_image004[8]

 

Como podemos ver, Item NO se lleva bien con los nulos, así que la solución pasa por trabajar con el método de extensión Field, que además nos evitará también tener que convertir explícitamente las columnas a un tipo concreto.

 

El ejemplo anterior de comparación quedaría con Field quedaría de la siguiente forma, donde vemos que ya no necesario comprobar nulos:

 

        Dim q =

            From r In customers.AsEnumerable

            Where r.Field(Of String)("CompanyTaxCode") = "13245"

            Select CompanyTaxCode = r.Field(Of String)("CompanyTaxCode")

 

Sin embargo, si agregamos el campo CustomerType a la lista de selección seguimos obtenido un error de conversión “La conversión especificada no es válida”:

clip_image006[8]

Para solucionar esto, lo que hay que hacer es utilizar Nullable(Of Int32) en vez de Int32.

        Dim q =

            From r In customers.AsEnumerable

            Select

                CompanyTaxCode = r.Field(Of String)("CompanyTaxCode"),

                CustomerType = r.Field(Of Int32?)("CustomerType")

 

Por otro lado, el tipo de la columna suministrado a Field tiene que ser exactamente un tipo equivalente al tipo subyacente. Es decir, si CustomerType guardara números pero fuera del tipo nvarchar (en sql), la conversión a Int32 o Int32? fallaría porque no es capaz de realizar una conversión implícita.

Para finalizar, otro método muy interesante es AsDataView, que a partir de la consulta de Linq To DataSet, convierte la consulta a un objeto DataView.

        Dim q =

            From r In customers.AsEnumerable

        Dim view = q.AsDataView()

 

Este método no es lo que parece y tiene bastantes limitaciones. Por ejemplo, no admite tipos anónimos, así que la siguiente consulta no funcionaría:

clip_image008

Más información sobre AsDataView en el siguiente enlace.

Siendo así, espero que este post venga acompañado más adelante de otro donde explique cómo enlazar consultas de Linq To DataSet a controles enlazados a datos, pero eso será otro día. Esto es hoy y ya está disponible el post Linq To DataSet: Comparándolo con su antecesor y enlazando a datos.

Un saludo!

No hay comentarios:

Publicar un comentario