Mostrando entradas con la etiqueta Linq. Mostrar todas las entradas
Mostrando entradas con la etiqueta Linq. Mostrar todas las entradas

lunes, 19 de septiembre de 2011

Linq To DataSet: Convirtiendo nuestra consulta a DataTable

Hoy he estado trabajando con Linq To DataSet y tenía la necesidad de devolver las consultas Linq como objetos DataTable.

Para ello he visto que hay disponibles distintas opciones.

Partiendo del siguiente código:

        Dim q =

            From r In customersDataTable.AsEnumerable

            Where r.Field(Of String)("CustomerID") = "4004008"

 

        Dim table1 As DataTable = q.AsDataView.Table

        Dim table2 As DataTable = q.AsDataView.ToTable

        Dim table3 As DataTable = q.CopyToDataTable

 

table1 devolverá el objeto DataTable llamado “customersDataTable”, así que el filtro especificado en la consulta de Linq no será efectivo, luego no utilizaré esta forma.

table2 convierte el resultado de la consulta a un objeto DataTable donde sí se aplica el filtro.

table3 copia el resultado de la consulta a un objeto DataTable donde de nuevo, sí se aplica el filtro.

En este punto, está claro que excepto la primera forma, las 2 siguientes son válidas. CopyToDataTable tiene la ventaja de que trabaja con tipos anónimos (Ni CopyToDataTable ni AsDataView soportan tipos anónimos).

Sin embargo, me he encontrado con algunas situaciones delicadas.

Si el número de filas devueltas por la consulta Linq es 0, entonces CopyToDataTable no funcionará. Sin embargo, AsDataView no tiene problemas si la consulta no devolvió ningún registro.

clip_image001

Por otro lado, AsDataView, además de no llevarse bien con los tipos anónimos, tampoco se lleva bien con las proyecciones. Sin embargo, CopyToDataTable no tiene problemas con las proyecciones y funciona correctamente.

clip_image003

Por hoy nada más

Un saludo!

domingo, 18 de septiembre de 2011

Linq To DataSet: Comparándolo con su antecesor y enlazando a datos

En el anterior post Linq To DataSet, contamos las bondades que nos ofrece este proveedor de Linq para la búsqueda y ordenación de filas en un objeto DataTable. Sin embargo, me gustaría enfrentar las posibilidades actuales de filtrado y ordenación de las que disponemos actualmente en un objeto DataTable contra las nuevas y mejoradas opciones que nos ofrece Linq To DataSet.

Siendo así, haremos un breve repaso sobre las propiedades y métodos más relevantes que, de forma nativa, ofrece un DataTable para la búsqueda, filtrado y ordenación de filas, para finalmente enfrentarlos a sus equivalentes en Linq To DataSet, y así convencernos plenamente de la potencia y superioridad que ofrece el proveedor de Linq para DataSet.

En un objeto DataTable, la única operación de búsqueda de filas disponible es el método Select.

DataTable.Select(filterExpression[, sort])

El método Select devuelve un array de objetos DataRow que cumple el filtro especificado y, opcionalmente, ordenado según el criterio de ordenación especificado.

Existe otra sobrecarga de Select que permite devolver filas a partir de un criterio de búsqueda y según el estado de las filas a partir del enumerado DataViewRowState.

La sintaxis para la expresión de filtro y los operadores disponibles puedes encontrarla aquí.

Cualquier objeto DataTable tiene automáticamente disponible un objeto DataView asociado en la propiedad DataTable.DefaultView. En cualquier caso, somos libres de crear tantos objetos DataView asociados a un DataTable como creamos oportuno.

Un objeto DataView es simplemente una lista de punteros a objetos DataRow del DataTable asociado, con un filtro y ordenación específico. Buscando una equivalencia, serían como las vistas en una base de datos.

De este modo, al poder tener distintos objetos DataView asociados al mismo DataTable lo que conseguimos es poder tener distintos grupos de filas ordenadas y filtradas por diferentes criterios.

Como hemos dicho antes, bien podemos trabajar sobre el objeto DataView por defecto (DataTable.DefaultView) o bien crear un nuevo objeto DataView.

Para crear un nuevo objeto DataView:

Dim miDataView As New DataView(miDataTable)


Antes de comenzar con ejemplos más concretos sobre DataView cabría comentar que un objeto DataView es una colección de objetos DataRowView y que cada objeto DataRowView permite el acceso al objeto DataRow que está apuntado a través de su propiedad Row.

clip_image001

Por otro lado la propiedad DataView.Table devuelve el objeto DataTable al que está asociado el objeto DataView.

Finalmente en el objeto DataView ya sí disponemos de más y mejores métodos para la búsqueda, ordenación y filtrado de filas:

  • RowFilter
  • Sort
  • ApplyDefaultSort
  • Find
  • FindRows

Con la propiedad RowFilter podemos filtrar a una expresión dada.

        ' Vista por defecto

        customersDataTable.DefaultView.RowFilter = "LockOrder = 1"

 

        ' Nueva vista

        Dim miDataView As New DataView(customersDataTable)

        miDataView.RowFilter = "LockOrder = 1"

 

        ' Eliminar filtro

        customersDataTable.DefaultView.RowFilter = String.Empty

        miDataView.RowFilter = String.Empty

 

Con la propiedad RowStateFilter podemos filtrar filas por un estado concreto según DataViewRowState.

Con la propiedad Sort podemos ordenar el conjunto de resultados de nuestro DataView.

        Dim miDataView As New DataView(customersDataTable)

        miDataView.Sort = "CompanyID DESC, CustomerID DESC"

 

El método Find busca según el criterio de ordenación actual y devuelve el primer índice del objeto DataRowView encontrado o -1.

Find necesita que previamente se haya especificado un criterio de ordenación que será sobre el que realice la búsqueda.

        Dim miDataView As New DataView(customersDataTable)

        miDataView.Sort = "CustomerID"

 

        Dim index As Integer = miDataView.Find("4309463")


Si la ordenación se ha realizado por múltiples campos, será necesario suministrar valores para cada uno de estos campos:

        Dim miDataView As New DataView(customersDataTable)

        miDataView.Sort = "CompanyID, CustomerID"

 

        Dim values(1) As String       

        values(0) = "520" ' CompanyID

        values(1) = "4309463" ' CustomerID

        Dim index As Integer = miDataView.Find(values)


Con el método Find encontramos el primer índice de una fila, pero si lo que queremos es obtener todas las filas que cumplan el criterio especificado (no sólo la primera), necesitamos utilizar el método FindRows.

        Dim miDataView As New DataView(customersDataTable)

        miDataView.Sort = "LockOrder"       

 

        Dim foundRows() As DataRowView = miDataView.FindRows("False")

        If foundRows.Length > 0 Then

            ' TODO Hacer algo

        End If


En este ejemplo quizás te extrañe la condición de búsqueda a “False”, pero es así como la expresión de filtro quiere comparar campos de tipo bit.

Por último, la propiedad ApplyDefaultSort indica si se va a utilizar o no el orden predeterminado. Aquí la pregunta es ¿Cuál es el orden predeterminado? Pues es el orden que tuviera el objeto DataTable asociado,y la única manera que hay de establecer este orden en el objeto DataTable es con la propiedad DataTable.PrimaryKey. De este modo, si nuestro objeto DataTable tiene establecida la propiedad PrimaryKey podremos buscar con Find sobre ella, si aplicamos la ordenación por defecto.

        Dim primaryKey() As DataColumn = { _

            customersDataTable.Columns.Item("CompanyID"), _

            customersDataTable.Columns.Item("CustomerID")}

        customersDataTable.PrimaryKey = primaryKey

 

        Dim miDataView As New DataView(customersDataTable)       

        miDataView.ApplyDefaultSort = True

 

        Dim values() As String = {"520", "4309397"}

        Dim index As Integer = miDataView.Find(values)


Habiendo ya explicado las propiedades y métodos principales para la búsqueda, ordenación y filtrado de filas en un objeto DataTable, ahora es momento de ver cómo realizar estas mismas tareas con Linq To DataSet.

Lo primero a mencionar es que con Linq To DataSet trabajaremos directamente sobre el objeto DataTable y no utilizaremos nunca el objeto DataView. Además, los conceptos de filtrado y ordenación conviven en la misma expresión de consulta como un sola operación que devuelve un conjunto de resultados.

        Dim q As Data.EnumerableRowCollection(Of DataRow)

 

        q =

            From r In customersDataTable.AsEnumerable

            Where

r.Field(Of String)("CustomerID").StartsWith("4309")
AndAlso r.Field(Of Boolean)("LockOrder") = True

            Order By

r.Field(Of String)("CompanyID"),
r.Field(
Of String)("CustomerID") Descending

            Select r


En este ejemplo, hemos filtrado por una condición compleja y además hemos ordenado los resultados, todo ellos sin importarnos la ordenación predeterminada, si los campos de filtro son parte de la ordenación, etc.

Como podrás comprobar, con Linq To DataSet todo es más sencillo. Si aún no lo has leído, insisto en que visites el post Linq To DataSet para familiarizarte con la sintaxis.

Incluso el anterior código podría ser más compacto si utilizamos la inferencia de tipos (como es habitual) para la variable q y además omitimos la cláusula Select.

        Dim q =

            From r In customersDataTable.AsEnumerable

            Where

r.Field(Of String)("CustomerID").StartsWith("4309")
AndAlso r.Field(Of Boolean)("LockOrder") = True

            Order By

r.Field(Of String)("CompanyID"),
r.Field(
Of String)("CustomerID") Descending


A partir de aquí podrías iterar sobre el conjunto de resultados para realizar las operaciones que creas oportunas.

        For Each r In q

            Response.Write(String.Format("CustomerID {0}", r.Item("CustomerID")))

        Next

 
En cualquier caso, la mitad de las veces que utilizo un DataTable es para enlazarlo a un control de datos. Con independencia de que se pueda enlazar la consulta Linq a un control, si queremos seguir enlazando objetos DataView (recuerda que cuando enlazas un DataTable a un control de datos, en realidad estás enlazando DataTable.DefaultView), lo que tenemos que hacer es transformar nuestra consulta a un objeto DataView con el método DataTable.AsDataView.

Este método (que está disponible si agregamos la referencia al ensamblado System.Data.DataSetExtensions), convierte nuestra consulta de Linq To DataSet en un objeto DataView.

        Dim q = From r In customersDataTable.AsEnumerable          

 

        Return q.AsDataView


El método tiene algunas limitaciones, como que no permite consultas que devuelvan tipos anónimos ni operaciones de combinación.

clip_image003

Por otro lado, también tenemos disponible el método CopyToDataTable que copia las filas del resultado de la consulta en un nuevo objeto DataTable. Es importante reseñar que CopyToDataTable “copia”, mientras que AsDataView no copia sino que “apunta” a filas. Es decir, cambios realizados en las filas del objeto DataView (con AsDataView) se verán reflejados en el objeto DataTable asociado, mientras que con CopyToDataTable estamos hablando de un nueva nuevo DataTable que nada tiene que ver con el original.

Al igual que AsDataView, CopyToDataTable tampoco puede copiar objetos anónimos ni operaciones de combinación, pero para este método podemos encontrar una implementación personalizado en MSDN para salvar estas limitaciones (esto es posible porque “creamos” un nuevo objeto DataTable, mientras que con AsDataView no es posible porque cada objeto DataRowView tiene que apuntar forzosamente a un objeto DataRow).

Aunque me he enrollado mucho con las limitaciones de AsDataView y CopyToDataTable, lo cierto es que si quieres enlazar tu consulta Linq a un control de datos en raras ocasiones querrás utilizar tipos anónimos, así que no hay excusa para no utilizar Linq To DataSet.

Un saludo!

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!