LINQ

Tutorial de LINQ y Entity Framework


Con el artículo anterior finalizamos con la introducción a LINQ y Entity Framework. A lo largo de esta serie de artículos hemos aprendido desde qué es LINQ hasta su aplicación a objetos, ficheros XML, bases de datos SQL Server y, por extensión, a cualquier otra fuente de datos.

Esta entrada servirá de recopilatorio y puerta de entrada a todo lo que hemos visto acerca de estas tecnologías a lo largo de los últimos días.

  1. Introducción
  2. Construcción de ficheros XML mediante LINQ
  3. Sentencias en LINQ
    1. Selecciones simples. Tipado implícito y tipos anónimos.
    2. Joins y multiselects
    3. Agrupaciones (group by)
    4. Particionado. Delegados y expresiones lambda.
    5. Funciones de agregación (count, sum, average, max, min)
    6. Ordenación (order by)
    7. Operaciones sobre conjuntos
    8. Cuantificadores, generadores y conversores
  4. LINQ to SQL
    1. Mapeo objeto-relacional
    2. Relaciones
    3. Consultas compiladas. Ejecución nativa de SQL. Procedimientos almacenados
    4. Modificación de datos (insert, update, delete)
  5. Entity Framework
    1. Creación de un Entity Model
    2. ObjectContext y Entity SQL
    3. Select, Insert, Update, Delete
    4. Mapeo mediante procedimientos almacenados
    5. Enlazado y desenlazado de entidades. Estados
    6. Entity Model y Data Services

Entity Framework (V): Enlazado y desenlazado de entidades. Estados


Una vez que tenemos claro que un DbContext es el encargado de velar por la integridad en el esquema objeto-relacional, ¿qué ocurriría si manejamos objetos que, por alguna razón, escapan a su control? Nos referimos, por ejemplo, al envío de los datos de un cliente a través de un formulario web y ponernos a la espera de que el usuario los modifique en otra página distinta. Este escenario implicaría, con toda seguridad, que el DbContext en el que se recuperó el objeto sea distinto al DbContext que se encargará de modificarlo.

Por lo tanto, tendremos dos opciones a la hora de tratar este problema:

  • Volver a recuperar el objeto completo, copiar todos los datos del objeto enviado por el usuario al objeto que acabamos de recuperar y guardar los cambios.
  • Enlazar directamente el objeto al DbContext y decirle que actualice los cambios.

Para la primera opción no es necesaria mucha explicación, puesto que se trata de una modificación normal y corriente. Para la segunda opción habrá que trabajar un poco más, pero se considera una opción más correcta. Además, el proceso dependerá de si estamos haciendo uso de un ObjectContext (versión anterior a 4.1) o un DbContext (versión posterior a ésta).

(más…)

Entity Framework (IV): Mapeo mediante procedimientos almacenados


En el anterior artículo aprendimos cómo mapear un procedimiento almacenado en nuestro Entity Model y hacer uso de él mediante una llamada explícita para eliminar en cascada todos los elementos asociados tanto a los clientes como a los pedidos.

Entity Framework proporciona la opción de personalizar los métodos de inserción, eliminación y actualización a través de procedimientos almacenados, haciendo que éstos se realicen mediante la sintaxis natural de Entity Framework pero permitiéndonos a nosotros indicar, a través de procedimientos almacenados, la forma de hacerlo.

Es importante saber que Entity Framework tiene una política de todo o nada para hacer uso de esta característica, es decir: si se decide utilizar un procedimiento almacenado para realizar las eliminaciones, será obligatorio definir también los métodos de inserción y actualización.

Ya definimos antes un procedimiento para las eliminaciones, llamado sp_Cliente_DeleteCascade. Codificaremos ahora un procedimiento almacenado para realizar las inserciones y otro para las actualizaciones.

El procedimiento de inserción recibirá como parámetros los componentes del registro salvo el identificador, que será generado automáticamente. Además, devolverá como valor de retorno la llamada al método SCOPE_IDENTITY(), que almacenará el valor del IdCliente generado para el registro insertado. A este valor le daremos un nombre para poder identificarlo en nuestro mapeo, por ejemplo, «id».

(más…)

Entity Framework (III): Select, Insert, Update, Delete


Pese a que LINQ to Entities es bastante parecido a LINQ to SQL, a la hora de trabajar con ambas tecnologías es necesario conocer las diferencias más importantes a nivel práctico (dejaremos los fundamentos teóricos a un lado). Ambas tecnologías tienden a la convergencia, puesto que Microsoft está intentando coger lo mejor de cada una de ellas y adaptarlo a ambos mundos. Sin embargo, siguen existiendo diferencias importantes, especialmente en las primeras versiones de LINQ to Entities.

Select. Carga diferida explícita.

Las primeras versiones de Entity Framework contenían ciertas carencias que, con posteriores actualizaciones, han sido solventadas y corregidas. Una de las carencias más importantes se correspondería con la imposibilidad de las versiones anteriores a 4.1 de realizar una carga automática de los elementos referenciados por un objeto. Es decir, si tenemos el siguiente código:


            // Instanciamos el contexto
            var contexto = new testdbEntities();

            // Lanzamos una consulta
            var clientes = from cliente in contexto.Clientes
                           select cliente;

            // Recorremos los clientes
            foreach (Cliente cliente in clientes)
            {
                Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tAÑO NAC: {2}",
                    cliente.IdCliente, cliente.Nombre, cliente.FechaNacimiento.Year));

                // Recorremos los pedidos
                foreach (Pedido pedido in cliente.Pedidos)
                {
                    Console.WriteLine(string.Format("\tPEDIDO: {0}\tFECHA: {1}",
                        pedido.IdPedido, pedido.FechaPedido));

                    // Recorremos las líneas de pedido
                    foreach (LineaPedido linea in pedido.LineasPedido)
                    {
                        Console.WriteLine(string.Format("\t\tPRODUCTO: {0}\tCANTIDAD: {1}\tTOTAL: {2}",
                            linea.Producto.Descripcion, linea.Cantidad, (linea.Producto.Precio*linea.Cantidad)));
                    }
                }
                Console.WriteLine(" -----------------------------------------\n");
            }

 

(más…)

Entity Framework (II): ObjectContext y Entity SQL


Una de las mayores ventajas de Entity Framework es que es una tecnología agnóstica respecto a la base de datos que tiene por debajo. Con ADO.NET era necesario utilizar clases específicas para cada base de datos (SqlCommand para SQLServer, OracleCommand para Oracle, etc.). Sin embargo, Entity Framework no se casa con nadie: hace uso de elementos genéricos (EntityConnection, EntityCommand, etc.) que genera instrucciones en un lenguaje intermedio denominado Entity SQL, que es muy similar al lenguaje SQL estándar.

En última instancia, el ADO.NET de toda la vida estará trabajando por debajo, pero Entity Framework establece una capa de abstracción superior que evita la necesidad de atarnos a una fuente de datos concreta. No obstante, es posible descender un poco en el nivel de abstracción y, en lugar de hacer uso de LINQ to Entities tal y como hicimos en el artículo anterior, lanzar directamente consultas sobre el ObjectContext usando para ello Entity SQL.

Dependiendo de la versión de Entity Framework que se esté usando, esto se realizará de un modo u otro, ya que la versión 4.1 introdujo como novedad la utilización del DbContext por defecto, envolviendo el ObjectContext dentro de él.

Por lo tanto, si queremos acceder al ObjectContext del DbContext que el diseñador habrá generado por nosotros, será necesario realizar un casting explícito a IObjectContextAdapter y acceder a su propiedad ObjectContext, tal y como mostramos en el siguiente ejemplo, en el que se ejecuta una sentencia Entity SQL en la que se recuperan todos los clientes de la tabla Clientes.


            // Instanciamos el DbContext
            var dbContext = new testdbEntities();

            // Extraemos el ObjectContext del DbContext (a partir de Entity Framework 4.1)
            var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;

            // Ejecutamos una sentencia de Entity SQL que recupere todos los clientes
            string sqlQuery = "SELECT VALUE c FROM Clientes AS c";
            var clientes = new ObjectQuery<Cliente>(sqlQuery, objectContext);

            foreach (Cliente cliente in clientes)
            {
                Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tAÑO NAC: {2}",
                    cliente.IdCliente, cliente.Nombre, cliente.FechaNacimiento.Year));
            }

El nombre de la tabla Clientes está en plural porque es el nombre que recibe la propiedad en la clase de contexto generada por el editor, no porque sea el nombre de la tabla de la base de datos. Recordemos que, Entity SQL realiza consultas sobre entidades, es decir, objetos, no sobre la base de datos.

Nuevamente, comprobamos que la consulta recupera los datos correctamente.

Entity Framework (I): Creación de un Entity Model


Entity Framework es la evolución natural de ADO.NET hacia el tratamiento de los datos almacenados en una base de datos relacional a través del paradigma objetual. Por lo tanto, se trata, al igual que LINQ to SQL, de un mapper objeto-relacional que es capaz de abstraer las tablas y columnas de una base de datos y tratarlas como objetos u entidades y relaciones.

Una entidad, por tanto, no es más que un objeto persistente, que cumple las condiciones de unicidad e identidad.

Añadiendo un Entity Data Model

Lo primero que deberemos hacer una vez que hayamos creado nuestro proyecto (por ejemplo, de consola) será añadir un nuevo Entity Data Model. Para ello haremos click derecho sobre nuestro proyecto y seleccionaremos la opción Add > New Item…

Una vez en el diálogo de selección, navegamos hasta la sección Data y seleccionamos el elemento ADO.NET Entity Data Model, al que le daremos un nombre identificativo.

(más…)

LINQ to SQL (IV): Modificación de datos


Antes de la aparición de LINQ, la filosofía de ADO.NET estaba orientada a datos. De este modo, cuando era necesario modificar o eliminar un registro, nuestro afán era detectar la clave primaria de ese elemento y generar, bien de forma estática, bien de forma dinámica, una sentencia UPDATE o DELETE para que la fuente de datos realizase la operación pertinente sobre ella.

La aparición de los mappers objeto-relacionales como LINQ to SQL intentan hacer algo difícil: desterrar ese concepto de nuestra cabeza y obligarnos a pensar exclusivamente en objetos.

Esta diferencia, al implicar un cambio de paradigma, puede resultar duro. Para ilustrar la diferencia entre ADO.NET básico y LINQ to SQL, veamos un ejemplo.

Anteriormente podíamos usar un DataAdapter para mapear una tabla de base de datos dentro de un DataTable. Y era posible, a partir de ese DataAdapter, obtener dos copias de un mismo conjunto de datos, es decir:


            //Obtenemos la cadena de conexión de App.config o Web.config
            string cadenaConexion = ConfigurationManager.ConnectionStrings["TestDb"].ConnectionString;

            // Creamos una conexión a partir de la cadena de conexión
            using (SqlConnection conexion = new SqlConnection(cadenaConexion))
            {
                // Declaramos una consulta
                string sqlSelect = "select * from Cliente";

                // Abrimos la conexión y generamos un SqlCommand con la conexión y la consulta
                // Instanciamos un SqlDataAdapter a partir del SqlCommand
                conexion.Open();
                SqlCommand commandSelect = new SqlCommand(sqlSelect, conexion);
                SqlDataAdapter dataAdapter = new SqlDataAdapter(commandSelect);

                //Declaramos dos DataTables y los rellenamos con el DataAdapter
                DataTable dt1 = new DataTable();
                DataTable dt2 = new DataTable();

                dataAdapter.Fill(dt1);
                dataAdapter.Fill(dt2);

                conexion.Close();
            }

(más…)

LINQ to SQL (III): Consultas compiladas, ejecución SQL y procedimientos almacenados


Compilación de consultas

En ocasiones podemos encontrarnos con consultas LINQ cuyo coste computacional puede ser alto, haciendo que el sistema se sobrecargue. LINQ to SQL proporciona algunas herramientas que mitigan, dentro de lo posible, el impacto de este tipo de operaciones sobre el rendimiento. Una de ellas es la compilación de consultas.

Es posible precompilar una consulta LINQ, de modo que durante su primera invocación realice un plan de ejecución que pueda mantenerse cacheado para ser reutilizado posteriormente. Así, la siguiente consulta:


            int idCliente = 2;

            var consulta = (from cliente in dbContext.Cliente
                            where (cliente.IdCliente == idCliente)
                            select cliente).FirstOrDefault();

Podría ser compilada y almacenada en una variable, que podrá ser reutilizada posteriormente como si fuese un método normal y corriente. Vemos, además, que la consulta puede parametrizarse, de modo que es posible asignarle cierto grado de generalización.


            var getClientePorId = CompiledQuery.Compile(
                (Testdb contexto, int id) =>
                    (from cliente in contexto.Cliente
                     where (cliente.IdCliente == id)
                     select cliente).FirstOrDefault()
                );

(más…)

LINQ to SQL (II): Relaciones


Ahora que ya sabemos cómo mapear una base de datos relacional en un conjunto de objetos, es momento de profundizar en algunos conceptos y procesos relacionados con esta transformación. El primero de ellos tiene que ver con la forma de tratar las relaciones entre las tablas.

Generalmente, una relación entre dos tablas se establece a través de un campo de la clase que referencia, al que denominamos clave foránea, que «apunta» (toma el valor) de la clave primaria (que identifica cada registro de forma única) de otra tabla.

Así, en el siguiente esquema, el campo IdCliente identifica de forma única a cada cliente, mientras que el campo IdPedido hace lo propio con cada pedido. Sin embargo, la tabla Pedido posee también un campo IdCliente, cuyo valor coincidirá con el campo IdCliente de la tabla Cliente perteneciente al registro que quiere referenciar.

Es decir. Si tenemos los siguientes registros:

Vemos que el campo IdCliente de la tabla Pedido «referencia» al campo IdCliente de la tabla Cliente indicando cuál es el cliente que ha realizado el pedido. A través de operaciones como joins, es posible, a partir del Id del cliente almacenado en el pedido, recuperar los datos del cliente, como su nombre o fecha de nacimiento.

(más…)

LINQ to SQL (I): Mapeo Objeto-Relacional


A estas alturas ya debemos de tener una idea bastante precisa del funcionamiento básico de LINQ. Hemos visto cómo lanzar consultas a listados de objetos (LINQ to Objects) e incluso algún ejemplo sobre cómo iterar sobre ficheros XML (LINQ to XML). Todas estas operaciones tienen un factor común: se realizaban sobre listados que implementaban la interfaz IEnumerable.

Además de poder lanzar consultas sobre listados de objetos, LINQ nos permite una opción aún más potente: lanzar consultas sobre bases de datos SQL Server mediante LINQ to SQL, que no es más que un mapper objeto-relacional, es decir, encargado de transformar sentencias LINQ en expresiones T-SQL y viceversa.

La potencia de LINQ to SQL radica en que, además de permitir realizar operaciones de consulta sobre una base de datos, también es posible realizar inserciones, actualizaciones y eliminaciones de forma nativa. A continuación veremos cómo realizar esto.

La principal desventaja de usar LINQ to SQL es que nuestra base de datos ha de ser, obligatoriamente, SQL Server (no obstante, existen proveedores LINQ para otras bases de datos, como LINQ to Oracle o LINQ to MySQL (vía NuGet). En caso de que todo esto falle y que necesitemos un proveedor personalizado, siempre podremos desarrollar por cuenta propia nuestro propio proveedor. En cualquier caso, siempre podremos utilizar un mapper objeto-relacional más genérico, como por ejemplo Entity Framework, del que hablaremos en posteriores artículos.

En este primer acercamiento a LINQ to SQL hablaremos de cómo establecer la relación entre nuestro programa (orientado a objetos) y nuestra base de datos (relacional). Para ello disponemos de varias posibilidades, que mostraremos a continuación:

(más…)

Sentencias en LINQ (VIII): Cuantificadores, generadores y conversores


Hasta ahora hemos visto las operaciones más comunes que pueden realizarse mediante LINQ. Por lo tanto, sólo nos queda echarle un vistazo a un subconjunto de sentencias de carácter más auxiliar, tales como la cuantificación, generación y conversión de elementos.

Any

El método Any toma como argumento un delegado o expresión lambda que tomará como parámetro un elemento de la lista y devolverá un valor booleano que indicará si al menos un elemento de la lista cumple la condición especificada.

Así, si quisiéramos saber si se ha vendido al menos un producto cuyo ID sea 3, podríamos optar por, bien crear un método que realice la comparación y que será pasado como delegado, bien utilizar una expresión lambda que realice la misma operación.


        // OPCIÓN 1: Usamos un delegado de una función que toma un elemento del listado como parámetro
        // y devuelve un booleano que devuelve el resultado de la comparación
        public bool PedidoConProductoIgualA3(LineaPedido linea)
        {
            return linea.IdProducto == 3;
        }
        bool existe = DataLists.ListaLineasPedido.Any(PedidoConProductoIgualA3);

        // OPCIÓN 2: Usamos una expresión lambda que realice la misma operación
                  bool existe = DataLists.ListaLineasPedido.Any(linea => (linea.IdProducto == 3));

(más…)

Sentencias en LINQ (VII): Operaciones sobre conjuntos


Las listas generadas mediante el álgebra relacional no son, en última instancia, más que conjuntos de datos. Y si nos ceñimos a una definición formal de conjunto, tenemos que se trata de una agrupación de elementos que es, a su vez, un elemento.

Sobre estos conjuntos pueden realizarse una serie de operaciones básicas:

  • Distinción: selección de los elementos del conjunto que no son iguales a ningún otro.
  • Unión: creación de un conjunto a partir de los elementos de dos o más conjuntos, sin tener en cuenta los elementos repetidos.
  • Concatenación: similar a la unión, pero teniendo en cuenta los elementos repetidos.
  • Comparación: comprueba si los conjuntos poseen exactamente los mismos elementos.
  • Intersección: creación de un conjunto a partir de los elementos comunes a dos o más conjuntos.
  • Diferencia: creación de un conjunto a partir de los elementos de un conjunto cuyos elementos NO se encuentran en otro conjunto.

La operación de distinción (Distinct) la hemos visto en artículos anteriores, por lo que nos centraremos en el resto de operaciones. Comenzaremos por realizar tres consultas compuestas. Una de ellas contendrá información sobre las compras realizadas con valor superior a 20 euros. Otra contendrá las compras por valor entre 7 y 22 euros. La tercera contendrá información sobre compras con valor inferior a 10 euros.

(más…)

Sentencias en LINQ (VI): Ordenación


Otra de las funcionalidades básicas de cualquier consulta es la ordenación de los datos. Por norma nos bastará con dos criterios: campo por el que filtrar y orden en el que filtrar (ascendente o descendente).

La cláusula LINQ para realizar el filtrado es orderby. Si no se añade ningún parámetro de orden, los campos se recuperarán por defecto de forma ascendente. En caso de querer un orden descendente, añadiremos la palabra reservada descending a continuación del campo por el que queremos ordenar. La palabra reservada ascending fuerza a que la ordenación sea, en todo caso, ascendente.


            var consulta = from cliente in DataLists.ListaClientes
                           orderby cliente.Nombre ascending
                           select cliente;

            foreach (var cliente in consulta)
            {
                Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tF.NACIMIENTO: {2}",
                    cliente.Id, cliente.Nombre, cliente.FechaNac));
            }

En este caso, los registros se ordenan por orden alfabético por el campo nombre, siempre de menor a mayor.

(más…)

Sentencias en LINQ (V): Funciones de agregación


Agregación, agrupación… son dos conceptos que parecen iguales pero que corresponden a características distintas dentro del álgebra relacional.

La agrupación, tal y como vimos en artículos anteriores, consiste en generar un conjunto de datos que poseen una característica común. Por ejemplo, agrupar los pedidos por cliente implicaría la obtención de varios subconjuntos (un subconjunto por cliente) compuestos por un dato que identifique unívocamente al cliente y sus pedidos asociados. Para realizar esta operación hacíamos uso de la sentencia group by.

La agregación está relacionada con la agrupación, ya que en lugar de proyectar un conjunto de datos asociados a otro, realiza una operación aritmética sobre uno o varios de estos datos. Por ejemplo, en lugar de obtener toda la información de los pedidos asociados a un cliente, una operación de agregación consistiría en obtener, por ejemplo el número de pedidos asociados a un cliente. Como podremos imaginar, todo cálculo de agregación debe tener asociada, por definición, una operación de agregación.

Las operaciones de agregación, por lo tanto, son de carácter matemático, y se suelen corresponder con las siguientes cinco operaciones:

  • Cuenta (Count): devuelve el número de registros pertenecientes a la agrupación.
  • Sumatorio (Sum): devuelve la suma de todos los valores de un campo numérico concreto perteneciente a la agrupación.
  • Máximo (Max): devuelve el máximo de los valores de un campo numérico concreto perteneciente a la agrupación.
  • Mínimo (Min): devuelve el mínimo de los valores de un campo numérico concreto perteneciente a la agrupación.
  • Media (Average): devuelve la media aritmética de un campo numérico concreto perteneciente a la agrupación.

Existen muchas otras posibles operaciones de agrupación, generalmente de carácter estadístico, como la varianza o desviación típica. Sin embargo, las cinco operaciones anteriores son las más extendidas en cualquier modelo de datos relacional, y por lo tanto, las más utilizadas.

(más…)

Sentencias en LINQ (IV): Particionado. Delegados y expresiones lambda.


El particionado es una característica que nos permite recuperar conjuntos concretos de una consulta a partir de un índice. Su funcionamiento sería similar al filtrado de no ser porque no hace uso de los datos en sí, sino del orden en el que éstos se almacenan.

Los métodos de particionado más útiles son los siguientes:

Take(n)

La sentencia Take devolverá los n primeros registros de la consulta. Así, si realizamos la siguiente consulta:

            var consulta = (from producto in DataLists.ListaProductos
                           select producto).Take(5);

Obtendremos los cinco primeros elementos de la consulta. Como podemos observar, el funcionamiento de este método es bastante sencillo.

Como punto a destacar: no se trata de una sentencia, sino de un método de la interfaz IEnumerable. Por tanto, puede aplicarse directamente a una lista sin necesidad de realizar la consulta LINQ que acabamos de hacer ahora mismo:

            var consulta = DataLists.ListaProductos.Take(5);

(más…)

Sentencias en LINQ (III): Agrupaciones (group by)


Tras saber cómo filtrar elementos, es buen momento para aprender a agruparlos. Agrupar elementos, como su propio nombre indica, es concentrar los datos de un registro a partir de una característica común. Por ejemplo, saber los pedidos que se corresponden a cada uno de los clientes.

Su sintaxis es la siguiente:


            var agrupacion = from p in DataLists.ListaPedidos
                             group p by p.IdCliente into grupo
                             select grupo;

Lo cual nos devolverá un listado de agrupaciones (objeto que implementa la interfaz IGrouping<tipoClave, tipoObjetoAgrupado>) compuesto por dos elementos principales:

  • Key: contiene la clave de la agrupación, es decir, el campo por el cual se está agrupando. En este caso se trataría del valor de p.IdCliente.
  • <Implícito>: el objeto en sí también es un listado compuesto por los objetos sobre los que itera la cláusula from, es decir, contenidos en DataList.ListaPedidos. En este caso sería un listado de objetos de tipo Pedido. Dado que están agrupados, el objeto grupo sólo contendrá aquellos objetos de la clase Pedido cuyo valor Pedido.IdCliente sea el mismo en todos los elementos de la lista.

(más…)

Sentencias en LINQ (II): Filtrado, Join y Multiselect


Hasta el momento hemos visto cómo realizar consultas sencillas sobre una colección, recuperando el objeto de rango sobre el que iteramos, componentes de éste o un nuevo objeto, bien de un tipo existente o anónimo. Es lo que se conoce en álgebra relacional como operaciones de proyección.

En el presente artículo aprenderemos a realizar operaciones de filtrado a través de la sentencia where. También reciben el nombre de operaciones de restricción.

Para el siguiente artículo haremos uso de las clases definidas en el artículo anterior, en el que devolvíamos la totalidad de los registros de una lista, siendo uno o varios campos de la entidad. A partir de ahora utilizaremos la sentencia where de LINQ, que es bastante similar a la que usamos en SQL.

Para filtrar una consulta en LINQ añadiremos la sentencia where <condición>. La condición, a diferencia de SQL, deberá utilizar el formato estándar del lenguaje, tal y como hacemos en una sentencia if o while. Así, si queremos recuperar aquellos productos cuyo precio sea superior a 7 Euros, lanzaríamos la siguiente consulta sobre la lista ListaProductos:


            var productosDeMasDeSieteEuros = from p in DataLists.ListaProductos
                                             where p.Precio > 7
                                             select p;

Simple, ¿verdad? El resultado sera un único elemento (Mochila escolar).

(más…)

Sentencias en LINQ (I): Selecciones simples. Tipado implícito y tipos anónimos.


Antes de profundizar un poco más en las posibilidades que ofrece LINQ para acceso a distintas fuentes de datos, conviene hablar un poco de las operaciones que podemos realizar con él.

LINQ, como buen lenguaje de consultas, permite la realización de múltiples operaciones sobre conjuntos de datos, tales como proyecciones, ordenaciones, particiones (útiles para realizar paginaciones) o agregaciones.

A continuación construiremos un pequeño conjunto de ejemplos que nos servirán como punto de inicio para comprender el funcionamiento de la sintaxis de este lenguaje. Comenzaremos creando en primer lugar un pequeño conjunto de datos sobre los que operar. Crearemos para ello cuatro entidades relacionadas entre sí. Asumiremos que tratamos de crear una aplicación para una papelería que quiere llevar un registro de clientes y ventas de productos, por lo que haremos uso de las siguientes entidades:

  • Cliente: simboliza un cliente de la papelería.
    • Id (int)
    • Nombre (string)
    • FechaNac (DateTime)
  • Producto: simboliza cada uno de los materiales que la papelería venderá a los clientes.
    • Id (int)
    • Descripcion (string)
    • Precio (float)
  • Pedido: simboliza un conjunto de productos proporcionados a un cliente concreto, por lo que se compondrá de una o más líneas de pedido.
    • Id (int)
    • IdCliente (int)
    • FechaPedido (DateTime)
  • LineaPedido: simboliza la venta de un número de un único producto concreto a un cliente en particular. Se asocia a un único pedido.
    • Id (int)
    • IdPedido (int)
    • IdProducto (int)
    • Cantidad (int)

Hemos definido las relaciones pese a que para este ejemplo no vamos a hacer uso de una base de datos relacional. Sin embargo, nuestro objetivo es simular esta opción (veremos cómo acceder a una fuente de datos relacional en posteriores artículos), por lo que haremos algo que nunca, bajo ninguna circunstancia, debería hacerse: codificar los datos en una clase de forma manual. Todo sea por el afán didáctico J

(más…)

Escribir ficheros XML mediante LINQ


Ayer veíamos la estructura de un fichero XML y cómo hacer uso del espacio de nombres System.Xml.Linq para generar un nuevo fichero XML. Anteayer, además aprendíamos de la versatilidad de LINQ, que nos permite iterar y filtrar sobre prácticamente cualquier cosa.

Hoy vamos a aunar los valores de ambos conceptos y vamos a generar (y consultar) un fichero XML a través de LINQ. Por ello es altamente aconsejable conocer previamente los conceptos explicados en los dos artículos anteriores.

Comenzaremos por el proceso de creación de un fichero XML. O más bien, de «cumplimentación». Hace ya unos añitos (¡cómo pasa el tiempo!) explicábamos en este mismo blog la forma de utilizar la reflexión para acceder a los elementos de un objeto (métodos, propiedades, atributos…). Hace unos días he actualizado ese artículo con información sobre cómo hacer uso de LINQ para realizar la misma operación que realizamos entonces (¡ah…! los tiempos del Framework 2.0…). Sin embargo, en este artículo iremos un poco más allá y aplicaremos esa misma funcionalidad para, a partir de un objeto, conseguir generar un fichero XML que desglose los elementos de cada miembro de un objeto.

Antes de meternos en harina con los ficheros XML, conviene centrarnos en la parte de LINQ. El objetivo es el siguiente: crear un fichero XML que almacene la información de todos los miembros de la clase de un objeto. La estructura objetivo será la siguiente:

  • <Clase>
    • <ConstructorMembers>
      • <Constructor name = «nombreConstructor» value=»void nombreConstructor()» />
    • <MethodMembers>
      • <Method name = «ToString» value = «System.String ToString()» />
      • <Method name = …

Es decir, desglosaremos la clase en secciones que, a su vez, contendrán elementos pertenecientes a esa sección (ConstructorMembers contendrá elementos Constructor y así sucesivamente), que a su vez contendrán dos atributos: name y value.

(más…)

Generar un fichero XML con XDocument


La tecnología LINQ tiene como principal atractivo su versatilidad: es capaz de realizar proyecciones y filtrado sobre cualquier colección iterable, así como de generar colecciones de objetos «al vuelo» de forma sencilla, tal y como vimos en el artículo anterior.

Hoy aprenderemos a hacer uso de uno de los grandes avances que Microsoft ha proporcionado, desde mi punto de vista, al mundo de la programación .NET: el espacio de nombres System.Xml.Linq. Para ello crearemos una pequeña función que, haciendo uso de LINQ y Reflection, recorra todos los elementos de un objeto y genere un fichero XML con sus metadatos.

Supongo que todos sabremos qué es un fichero XML y cuál es su estructura, pero no viene de más realizar una pequeña introducción a este tipo de documentos antes de aprender a bucear en su información y a generar ficheros de este modo.

El fichero XML

Un fichero XML (eXtensible Markup Language) es un fichero de marcas (como HTML) completamente personalizable que permite almacenar datos de forma esquemática y ordenada. Los elementos que nos interesan para entender este ejemplo son los siguientes:

  • Documento: engloba el fichero XML en sí. Se compone de una cabecera o prólogo y de un elemento o nodo raíz, del que colgarán (estarán anidados dentro de él) el resto de elementos del documento. En la imagen que puede verse más abajo, el elemento raíz sería «configuración».
  • Elemento o nodo: elemento principal de un fichero XML. Permite definir atributos y anidar otros elementos dentro de ellos. En la imagen inferior, los elementos del documento serían configuration, startup (que sería hijo directo de configuration) y supportedRuntime (que sería hijo directo de startup y nieto de configuration).
  • Atributo: asocia propiedades o características a un elemento, y se compone por un par clave-valor. En el caso inferior podemos ver algunos de los atributos, como «versión», que será un atributo del elemento supportedRuntime y cuyo valor será «v4.0».

XML

(más…)