BTS 2006: Integrando con SQL Server y Servicio Web.

Siguiendo con las capacidades de integración de Biztalk Server 2006 (BTS 2006) que estamos probando en el CIIN  en este post vamos a modelar un ciclo de gestión de pedidos de una organización. En concreto, en el escenario a modelar tenemos una aplicación de solicitud de pedidos que almacena los datos de los mismos en una BD SQL Server.  Estos datos de solicitud se envían  a un servicio web que se encarga de validar que el pedido puede ser servido comprobando si hay stock suficiente para ello. Una vez que el pedido es validado, el servicio web generará una factura que tiene que ser enviada en un cierto formato a él aplicativo de facturación.  Esta situación tan típica del “mundo real” es la que vamos a modelar e implementar de manera sencilla con BTS 2006 y las capacidades de integración comentadas.


Nuestro escenario de trabajo será el siguiente:


·         Recogemos los datos del pedido de una base de datos SQL Server.


·         Estos datos validamos y operamos con ellos mediante la invocación de un Servicio Web.


·         El servicio web nos devuelve unos datos de facturación que insertaremos en otra base de datos SQL Server.


Como hemos comentado, para realizar todos estos procesos utilizamos BTS 2006 y sus capacidades de integración. Así, nuestra primera tarea es poder extraer los datos de SQL Server utilizando puertos de BTS 2006.  Este proceso de integración con SQL Server lo explicó detalladamente mi compañero Juan Carlos en un post anterior (en este articulo solo detallaremos las diferencias de configuración pues la filosofía de trabajo es la misma). En nuestro caso la tabla de SQL de la que vamos a extraer los datos del pedido tiene la siguiente estructura:



Para extraer los datos, utilizamos un procedimiento almacenado  que es ejecutado desde un puerto de recepción de BTS 2006 (ya explicamos en el post citado las configuraciones necesarias para extraer y volcar datos desde / hacia una BD SQL Server).



Los datos que recogemos de la BD SQL Server tienen que ser enviados a un servicio web para validar que el pedido se puede servir. Por lo tanto, tenemos que modelar este proceso de negocio con las capacidades que nos da BTS 2006: recogida de los datos de SQL Server, envío de estos datos al servicio web de validación y en un cierto formato, y finalmente volcar los datos que nos devuelve el servicio web en una base de datos SQL Server para su explotación en la aplicación de facturación. Como hemos dicho,  este proceso de negocio lo modelaremos en BTS 2006 a través de Visual Studio 2005 y los artefactos necesarios: esquemas XSD, mapas de BizTalk en el que definir las transformaciones y el más importante, la orquestación que coordine la recogida de los datos del pedido de la base de datos, invoque al servicio web para que los valide, recoja de este la factura emitida y la entregue a la aplicación de facturación.



Por lo tanto nuestro siguiente paso será construir un proyecto de BizTalk utilizando Visual Studio 2005 e ir añadiendo los elementos mencionados.


Esquemas XSD


Una vez que tengamos creado el proyecto en el entorno de VS 2005, tenemos que insertar o crear los esquemas XSD que necesitamos para realizar los mapeos de datos necesarios en el proceso de negocio, y definir los mensajes que utiliza la orquestación:


·         Esquema correspondiente a los datos de solicitud del pedido, en este punto se nos plantea un problema, y es que necesitamos el esquema XSD correspondiente al archivo XML que nos generara BTS 2006 al extraer los datos del SQL Server, pero no disponemos directamente de ese esquema, aunque si podríamos obtener los mensajes XML que genera BTS 2006 en el proceso de extracción. Ante este problema una de las soluciones por las que podamos optar, es a hacer la aplicación escalable y obtener en primer caso el documento XML que genera BTS 2006, y una vez obtenido el documento XML utilizar una herramienta de Visual Studio 2005, que nos permite crear documentos XSD a partir de documentos XML bien formados.


Por lo tanto al puerto de recepción, que hemos configurado anteriormente, le añadimos un puerto de envío subscrito a él, de manera que todo mensaje que se entregue en el puerto de recepción sea recogido por el puerto de envío. Lo configuramos que se muestra en las siguientes figuras:


o   Configuración del puerto de envío, que a través del adaptador FILE volcará el archivo XML extraído en una carpeta.



o   Configuración de la suscripción del puerto de envío al puerto de recepción a través del correspondiente filtro.



De esta forma al iniciar la aplicación se nos creara un documento XML, con los datos insertados en la base de datos del SQL Server. Una vez creado este archivo en Visual Studio 2005 seleccionamos nuestro proyecto y escogemos la opción de añadir elemento- añadir elementos generados. Escogemos el grupo de esquemas y damos a añadir, se nos abrirá una ventana como esta.



En la opción de input file escogemos el archivo XML generado por BTS 2006 al extraer os datos del SQL Server y damos a OK, con lo que se generará el esquema XSD que cumplen los documentos XML que se extraigan de la BD SQL Server.



·         Esquema XSD de los datos que espera el servicio web, este esquema lo obtenemos directamente del servicio web, añadiendo la referencia web a nuestro proyecto de BTS 2006 igual que haríamos en otro proyecto de Visual Studio 2005.


·         Esquema XSD de los datos que devuelve el servicio web, ocurre lo mismo que en el caso anterior, al añadir la referencia web al proyecto de BTS 2006 tendremos acceso al esquema de vuelta con el que responde el servicio web después de realizar el procesado de los datos.


·         Esquema XSD de los datos que se van a volcar en la BD de facturación, aquí seguimos el mismo procedimiento que en el post de Juan Carlos, pero en nuestro caso utilizando un esquema con la siguiente estructura. Este esquema lo creamos de manera manual utilizando el editor que nos proporciona BTS 2006 (parte izquierda de la figura).



Mapas de BTS 2006


Nuestro siguiente paso será crear los mapas de BTS 2006 necesarios, cuya definición y propiedades podéis encontrar un post anterior mío.  Necesitamos dos:


·          El primer mapa permite pasar del mensaje XML con los datos del pedido al mensaje XML con los datos que espera el servicio web para comenzar a operar.


·         El segundo permite pasar del mensaje XML con los datos de facturación devuelto por el servicio web al mensaje XML que nos permitirá insertar los datos en la BD de facturación.


Cada mapa de BTS 2006 usa al menos 2 esquemas XSD, uno como origen y otro como destino. El primer mapa utiliza como origen el XSD correspondiente al documento XML con los datos que se extraen de la BD de pedidos, y como origen uno de los esquemas XSD que expone el servicio web para poder interactuar con él. En el segundo mapa, como origen utilizamos el esquema XSD de salida que expone el servicio web, y como destino el esquema XSD que cumplen los documentos XML con los datos de facturación. Para poder seleccionar los esquemas XSD que necesitamos en el mapa, utilizamos la herramienta BizTalk Type Picker que nos permite escoger los esquemas que tenemos en nuestra solución y los expuestos por cualquier referencia web que hayamos añadido.



Una vez que tenemos claro cuáles son los esquemas origen y destino para cada mapa, vamos a ver cómo serían dichos mapas:


·         Mapa1: Datos de Pedido – Entrada Servicio Web:



·         Mapa2: Salida Servicio Web – Datos de Facturación:



Orquestación


Nuestro siguiente paso es construir la orquestación que modela el proceso de de negocio. La orquestación tiene que ser capaz de recoger un mensaje XML con los datos procedentes del SQL Server, transformar esos datos para que el Servicio Web (WS) los entienda, comunicarse con el WS, recoger el mensaje procedente del WS con los datos validados, transformar esos datos para que se puedan insertar de nuevo en una tabla del SQL Server y finalmente mandar ese mensaje a un puerto físico para realizar la inserción en la tabla del SQL Server.


Para ello, tendremos que crear los mensajes con los que va a operar la orquestación para hacer efectivo el proceso de negocio. En este ejemplo necesitamos cuatro mensajes, uno de origen, otro de destino y después dos para comunicarse con el Servicio Web, el mensaje que se le manda al WS y el que recibe BTS 2006 del WS.



Estos mensajes con los que internamente trabaja la orquestación se van a corresponder con los documentos XML mencionados anteriormente a través de la configuración adecuada de sus propiedades. Para configurar cada mensaje simplemente  especificamos en la propiedad Message Type el esquema XSD o mensaje web (para el caso de utilizar referencias web) con el que se relaciona de manera unívoca:



Cuando tengamos configurado los mensajes,  utilizando el Diseñador de Orquestaciones empezaríamos a diseñar la orquestación, que tendría el siguiente aspecto.



Como vemos, la orquestación se compone de los siguientes elementos:


·         Port_Origen, puerto lógico de entrada por el que llegan los datos del pedido.


·         Recv_SQL, figura de tipo Receive que se enlaza con el puerto lógico de entrada y que es la que pasa los datos a la orquestación.


·         Transform_a_WS, figura de transformación que permite pasar de los datos originales del pedido a los datos que espera el servicio web. Esta figura es la que se encarga de hacer efectiva la transformación definida en el Mapa1.


·         Send_WS, figura de envío que se encarga de enviar los datos que espera el servicio web.


·         WS_Port, que es un puerto lógico bidireccional capaz de recibir y enviar datos. Este puerto recoge los datos destinados al servicio web, y envía los datos que este devuelve.


·         Transform_a_SQL, que es otra figura de transformación que se encarga de pasar de los datos enviados por el servicio web a los datos que espera la aplicación de facturación. Para ello, utiliza el Mapa2.


·         Snd_Destino, que es un puerto lógico de envío que se encarga de enviar los datos a la base de datos de facturación.


Nota: Aunque se hable de datos, en todo momento tenemos que tener presente que seestá trabajando con documentos XML entre los distintos elementos de la orquestación.


Configuración de los puertos lógicos y deploy de la solución


Finalmente solo nos falta de configurar los puertos lógicos de la orquestación, el puerto de recepción y el puerto de envió finales son muy sencillos de configurar y prácticamente solo es seguir el wizard que se nos abre cuando pinchamos en la admiración del send shape y receive shape. Sin embargo el puerto bidireccional que utilizamos para comunicarnos con el servicio web es algo más complicado de configurar, por lo que merece la pena que nos paremos a ver como es esta configuración.


Nos colocamos en una de zonas de puertos (Port Surface) y damos botón derecho, seleccionamos New Configured Port. Damos a siguiente y le ponemos un nombre, en nuestro caso le hemos llamado WS_port. El siguiente paso le configuramos como se muestra en la siguiente imagen.



Como podéis ver en la imagen anterior seleccionamos la opción Use an existing Port Type y escogemos Web Port Types. En ese punto no vendrá el tipo de puerto que está unido a nuestra referencia web.


Con esto ya tendremos configurada completamente nuestra orquestación, por lo tanto solo tenemos que construir y desplegar nuestro proyecto. Una vez hecho esto solo tendremos que realizar los ajustes en la consola de administración para tener nuestra aplicación configurada.


Juntándolo todo


Para realizar la inserción de los datos en la base de datos de facturación seguimos en el mismo procedimiento que explicamos en un post anterior. Además de configurar el volcado de datos, también debemos de configurar el puerto de envío físico para comunicarse con el Servicio Web. Este puerto será diferente a los que hemos utilizado, ya que no será solo de envío o solo de recepción, sino que debe de poder enviar y recibir de datos del Servicio Web. Para crearle vamos al nodo de Send Ports de nuestra aplicación, damos botón derecho y escogemos Static Solicit-Response Send Port.



Una vez creado tenemos que configurarle. Escogemos tipo de transporte SOAP, y en su configuración introducimos la url del Servicio Web con el que nos vamos a configurar, tal y como se muestra en la siguiente figura.



Por lo tanto solo nos falta configurar las propiedades de la orquestación. Para ello nos vamos a configurar su binding, en este caso tendremos 2 puertos de envío y un puerto de recepción, configuramos los binding como se muestra en la figura siguiente.



Para terminar, iniciamos la aplicación desplegada y configurada y si todo ha ido bien en la BD de facturación deberían aparecer los datos de la factura correspondiente al pedido realizado:



Los datos corresponden con el campo ID_Pedido de la primera tabla al que se le ha concatenado el string “FACT” y el valor importe se calcula multiplicando el campo Cantidad por una constante definida en Servicio Web. Con esto concluye este post sobre integración y capacidades de BizTalk Server 2006, espero que os sea de utilidad y un saludo a todos.


Anuar

BizTalk Server 2006 R2: Novedades!

Ayer asistí a un WebCast de Microsoft TechNet en el que Tomás Hernández nos contaba algunas de las novedades más importantes que vendrán con la R2 de BizTalk Server 2006 (BTS  2006 R2). Esta nueva release de BTS 2006 se espera que aparezca en septiembre de este año, y actualmente se encuentra en Beta 2, disponible para los partners y empresas que han entrado en el programa TAP (Technology Adoption Program) de Microsoft. Como veremos, algunas de las mejoras más relevantes están enfocadas hacia un mejor soporte de EDI por parte de BTS 2006, soporte de RFID, y por supuesto nuevos adaptadores basados en .NET Framework 3.0.


Si queréis acceder al webcast grabado, aquí os dejo el enlace. Veréis que hay otros enlaces relativos a BTS 2006.


¿Qué es BTS 2006 R2?


Como su nombre indica, es la nueva versión de BTS 2006 que amplía las funcionalidades de su predecesor:



·         Amplia notablemente las funcionalidades de su predecesor, a través de un mejor soporte para problemáticas específicas (EDI, RFID) y potenciando aún más sus capacidades de integración (WCF está plenamente soportado e integrado).


·         Mejora la gestión de los procesos de negocio (Business Process Management, BPM).


·         Mejor soporte EDI: Se mejora el adaptador EDI y se soportan más esquemas.


·         Soporte para RFID.


·         Soporte de WCF y WF.


·         Nuevo framework de adaptadores para construir adaptadores sobre la base de WCF.


Mejor Soporte EDI


BTS 2006 R2 mejorará notablemente el soporte de EDI de su predecesor:


·         Se soportan más esquemas de EDI, tanto de los estándares EDIFACT y X12 ya soportados, como el soporte de nuevos estándares: HIPAA (Sector Sanitario) y EANCOM (Sector Comercio).  En total, se soportarán más de 7000 esquemas EDI.


 
























































































































































Segmento


Estándar


Versión


Número de Esquemas Incluidos


Industria en General


EDIFACT


D93A


55


D94A


68


D94B


75


D95A


101


D95B


115


D96A


125


D96B


136


D97A


143


D97B


151


D98A


157


D98B


165


D99A


169


D99B


180


D00A


187


D00B


191


D01A


194


D01B


194


D02A


194


D02B


194


D03A


192


D03B


192


D04A


192


D04B


192


D05A


193


D05B


193


X12


2040


29


3010


39


3020


104


3030


161


3040


187


3050


225


3060


245


3070


273


4010


293


4020


302


4030


309


4040


314


4050


314


5010


318


5020


317


5030


317


Retail


EANCOM


EAN94


27


EAN97


46


EAN02


46


Sanidad


HIPAA X12N


401A1


16


 


·         Soporte de batching / debatching, es decir, poder gestionar mensajes EDI que tengan varias transacciones.


·         Nueva pipelines para integrar (envío / recepción) de manera nativa documentos EDI en BTS 2006. Esta pipeline se encarga de realizar el desensamblado de la de la información, parsing necesario y validación:


o   Dividir el mensaje en sus componentes.


o   Generar el XML de intercambio.



·         Mejor gestión de errores:


o   Información detallada de los errores producidos al procesar / generar EDI.


o   Soporte y  generación de asentimientos.


o   Detectar duplicados EDI y suspenderlos.


·         Se mejora la configuración de EDI, y como muestra este pantallazo del blog del equipo de B2B de Microsoft:



·         Posibilidad de extender los esquemas soportados en tiempo de diseño, lo que es muy útil a la hora de poder incluir nuevos valores en aquellos campos que almacenen codificaciones (Por ejemplo, para EDIFACT el Código de Identificador de Parte).



·         Y otras mejoras que podéis encontrar en el blog del equipo de B2B de BTS 2006. De hecho, todas las mejoras aparecen en este post.


Además de los puntos anteriores, un añadido de BTS 2006 R2 es que incluye soporte para el estándar AS2.


Soporte RFID


Una de las grandes novedades de BTS 2006 es el soporte de RFID que, como se comenta en este artículo, proporciona una infraestructura unificada para descubrir, comunicar y gestionar dispositivos RFID en plataforma Windows. Incluye los bloques necesarios para poder integrar estos dispositivos con aplicaciones existentes:


·         Identificación por radiofrecuencia.


·         Modelo de procesado de eventos RFID: almacenar, administrar y gestionar este tipo de eventos.


·         Se pueden aplicar conceptos y herramientas ya existentes en BTS 2006: reglas, alertas, transformaciones, etc.


·         Integración de la información RFID con WF y Data Warehouse.


·         Desarrollo en entorno desconectado: puedo crear la lógica de gestión de los dispositivos RFID sin tener que conectarlos a mi plataforma de desarrollo. De este modo, nos centramos en la capa lógica.


La arquitectura del soporte RFID en BTS 2006 R2 es la siguiente:



Los elementos más importantes de la arquitectura RFID son:


·         La capa DSPI (Device Service Provider Interface), que abstrae a la infraestructura RFID de BTS 2006 de todo lo que se coloque por debajo: dispositivos con tecnología RFID actual, sensores, futuros dispositivos, etc. Esta capa estará formada por un conjunto de API’s que permitirán que cualquier dispositivo RFID pueda interactuar con BTS 2006.


·         Como siempre, un motor de ejecución que se encarga de tratar y utilizar los datos enviados por los dispositivos RFID en los procesos de negocio de la organización. El motor se encargará de eliminar el ruido que pueda existir, filtrar, agregar y transformar datos RFID dentro de estos procesos de negocio. El motor de ejecución se compone de:


o   El motor de procesamiento de eventos que realiza la captura efectiva de los datos RFID.


o   El gestor de dispositivos, que se encarga de gestionar todos los dispositivos en el entorno de ejecución.


·         La infraestructura RFID de Microsoft, que da acceso al modelo de objetos y a las API’s que permiten diseñar, implementar, desplegar y gestionar soluciones RFID.


·         Un conjunto de diseñadores, herramientas y adaptadores que permitan crear distintos procesos de negocio en los que se requiera interacción con dispositivos RFID.


Soporte WCF


BTS 2006 R2 traerá un conjunto de adaptadores WCF que permitirán consumir y exponer servicios de este tipo en soluciones de BTS 2006. Hasta ahora, podíamos consumir servicios web a nivel de puerto y orquestación a través del SOAP Adapter, y exponer orquestaciones como servicios web. Con estos nuevos adaptadores, se podrán consumir servicios WCF a nivel de puerto y orquestación, así como exponer orquestaciones de BTS como servicios WCF.



Los nuevos adaptadores de WCF incluidos son:


·         WCF-WSHttp, que soporta WS-* sobre protocolo HTTP.


·         WCF-NetTcp, que soporta WS-* sobre TCP.


·         WCF-NetMsmq, que da soporte para transporte MSMQ.


·         WCF-BasicHttp, que habilita la compatibilidad con los servicios web tradicionales.


·         WCF-NetNamedPipe, que habilita la comunicación utilizando transporte NamedPipes.


·         WCF-Custom, que permite utilizar componentes WCF personalizados. Proporciona la infraestructura para crear nuestros propios adaptadores WCF personalizando binding y behaviors.


·         WCF-CustomIsolated, que habilita conectividad HTTP  fuera de los límites del IIS.


Además, las nuevas capacidades WCF de BTS 2006 R2 habilitan el uso de cabeceras WS-* en el enrutado de mensajes.


 .NET  3.0 Adapter Framework


Como su nombre indica, esta novedad de BTS 2006 R2 nos dará la infraestructura (API’s) necesaria para construir fácilmente nuestros adaptadores utilizando como base .NET Framework 3.0 (y en particular WCF) y poder simplificar la integración con sistemas LOB (SAP, Siebel,…), bases de datos (SQL Server, Oracle,…), colas de mensajería (MSMQ), etc.



La definición inicial de estos adaptadores se basará en el uso de asistentes que crearán la estructura inicial del adaptador que luego será extendida.



Este nuevo framework provee un conjunto de clases que facilitan la implementación de estos nuevos adaptadores  y que serán expuestos a terceros como canales de WCF.


BAM – WF y WCF


La BAM (Business Activity Monitoring, de la que espero hacer un post en el futuro) es el componente de BTS 2006 pensada para la gestión de BPM en dicha plataforma. En este sentido, proporciona las capacidades necesarias para definir y desplegar vistas de negocio sobre los procesos de negocio modelados con BTS 2006.  Para ello, es necesario vincular los procesos de negocio con estas vistas de negocio a través del uso de interceptores. Es en este punto dónde la R2 introducirá cambios en la BAM, puesto que incluirá interceptores para WF y WCF, de manera que además de los procesos de negocio modelados con la BAM podremos definir vistas de negocio sobre aquellos procesos modelados con WF y WCF.



Y hasta aquí lo que de momento se sabe sobre las novedades que vendrán con BTS 2006 R2, que son muchas y muy interesantes. Espero que el post os haya resultado interesante.

Integración de Reporting Services con SharePoint (II)

En el post anterior ya explicamos los pasos que hay que seguir para integrar SQL Server Reporting Services con SharePoint (WSS ó MOSS), ahora vamos a ver cómo podemos utilizar esta integración para poder crear y ver estos informes desde nuestro sitios SharePoint. Para ello vamos a utilizar las herramientas que nos ha añadido los add-in de Reporting Services, que son:




  • Content Type:


    • Report Data Source
    • Report Builder Model
    • Report Builder Report    

  • SQL Server Reporting Services Report Viewer Web Part

Lo primero que debemos realizar para generar los informes es la conexión a la base de datos y la definición del modelo de datos que vamos a utilizar, este proceso se puede desarrollar también desde Visual Studio creando un nuevo proyecto de tipo Report Model Project y después conectándolo con el servidor de SharePoint, pero nosotros lo vamos a diseñar desde el propio servidor realizando los siguientes pasos:



  • Nos creamos una librería de documentos en la que vamos a activar el uso de Content Type y vamos a añadir los Content Type de Report Data Source y Report Builder Model, para realizar la conexión a la base de datos y el modelo de datos a utilizar en los informes.
  • Una vez creada, pulsamos sobre la opción Report Data Source que nos aparece en el menú New de la librería de documentos y especificamos los parámetros de la conexión a la base de datos, que son establecer un nombre al fichero .rsds, poner el tipo del origen de datos, la cadena de conexión y las credenciales que utiliza el servidor de reporting para acceder al origen de datos.



  • Una vez que ya tenemos definido el fichero de acceso a la base de datos, vamos a crear el modelo de datos para utilizarlo en la generación de informes, para ello pulsamos sobre la opción Report Builder Model y nos abrirá la pantalla donde especificaremos el nombre del fichero .smdl que vamos a crear del modelo de datos y seleccionamos el fichero de la cadena de conexión a la base de datos definido anteriormente



  • Cuando haya terminado de generar el modelo de datos para los informes, podremos en sus propiedades modificar sus parámetros como la cadena de conexión, la seguridad, regenerar el fichero o realizar los informes directamente pulsando sobre Load in Report Builder


Una vez que ya tenemos definidos los ficheros de la conexión a la base de datos y del modelo de datos que vamos a utilizar, tenemos dos caminos para generar los informes:



  1. Desde el propio modelo de datos generado pulsando sobre la opción de sus propiedades Load in Report Builder.
  2. Desde una librería de documentos activando el Content Type de Report Builder Report

Al ejecutar cualquiera de estas dos opciones, lo que nos van a arrancar una aplicación Click-one que va a instalar y ejecutar el programa de Sql Server 2005 Reporting Services Report Builder para poder definir nuestros informes




Nota: Si lo ejecutamos desde la librería de documentos, al arrancar la aplicación debemos seleccionar el modelo de datos a utilizar en el informe.



Una vez tengamos el programa arrancado y el modelo de datos seleccionado, podremos ver todas las tablas con las que podemos trabajar y generar el informe, y tan solo arrastrando los campos a la plantilla, diseñando los filtros, parámetros, etc…. vamos diseñando el informe, y si pulsamos sobre la opción Run Report podemos ir viendo una vista previa del mismo.



Una vez que hayamos terminado de definir el informe y de guardarlo en la librería de documentos establecida para ello, podremos ver el informe desde SharePoint de dos formas diferentes, una pulsando directamente sobre el propio fichero generado que nos abrirá una nueva ventana del navegador con el informe, o añadiendo a la página principal la web part de SQL Server Reporting Services Report Viewer y seleccionando el informe que queramos visualizar.


LINQ To XML: Integración de XML!

Como no podía ser de otra forma, y después de hablar de las bases de LINQ y de LINQ To SQL, desde el CIIN nos faltaba comentar la “tercera pata” del proyecto LINQ: LINQ To XML. Lo de tercera pata va entrecomillado, porque ya tenemos a la vista nuevas capacidades (como LINQ To XSD) y nuevas siglas  que integran las siglas LINQ (CLINQ, para habilitar el uso de parte de las características de LINQ en C++/CLI, PLINQ, para definir una implementación de LINQ que permita la ejecución de código LINQ en múltiples CPU’s,  y seguro que algún componente más de LINQ sobre el que alguien ya está trabajando) y de las que espero poder comentaros cosas interesantes próximamente (especialmente de BLINQ).  Empecemos pues con LINQ To XML.


¿Qué es LINQ To XML?


Según la documentación oficial sobre LINQ, LINQ To XML es el componente específico de LINQ pensado para trabajar con información en formato XML cacheada en memoria, aprovechando las ventajas de los operadores estándar de consulta, y que además expone y añade nuevas funcionalidades para crear y manipular (a través de la integración en .NET Framework de toda la potencia que dan las transformaciones XPath y XQuery) de manera sencilla tanto documentos XML completos como porciones o fragmentos de una estructura XML. Por supuesto, como sucedía con LINQ To SQL, LINQ To XML va a permitir combinar consultas y transformaciones de documentos o fragmentos XML con consultas de otros orígenes de datos (relacionales o no). Estas prestaciones de LINQ To XML se apoyan en las siguientes características clave:


·         Las innovaciones del lenguaje (C# 3.0 y VB 9.0) que vimos en un post previo, así como en las características propias de LINQ (consultas integradas en el lenguaje).


·         Consultas a estructuras XML integradas en el lenguaje, es decir, tenemos la potencia de XPath y XQuery dentro de C# y VB.


·         Se sigue manteniendo la experiencia de DOM, pero se utiliza de un modo más sencillo:


o   Visión centrada en los elementos particulares de una estructura XML, frente a la visión anterior centrada en el documento.


o   Construcción funcional y a medida de estructuras XML. No necesito construir un documento XML completo, y puedo trabajar directamente con elementos XML al estilo XPath.


·         Las estructuras XML se crean de un modo dinámico, lo que simplifica su tratamiento.


·         Supone una representación en memoria más eficiente, que se integra con la infraestructura de lectura/escritura de System.Xml.


·         Se pueden cargar documentos XML existentes desde múltiples orígenes: XmlReader, TextReder, o  un simple String.


Siguiendo la misma referencia de otros post,  en la traducción de El Proyecto LINQ de Octavio Hernández, LINQ To XML define tres tipos de objetos en los que se basa la mayor parte de la integración de consulta contra estructuras XML.


·         XName, que habilita los mecanismos para trabajar de manera sencilla con los identificadores de espacios de nombres (QNames) utilizados en los nombres de elementos y atributos.


·         XElement, que viene a representar un elemento o nodo de una estructura / documento XML.


·         XAttribute, que es la representación de un atributo de un elemento.


Aunque estos tres tipos son las piezas claves de LINQ To XML, hay muchas más clases definidas en su jerarquía de clases (hasta la Preview de Mayo de 2006).



Después de esta introducción a lo que es LINQ To XML, en los siguientes apartados trataré de cubrir aspectos relativos a la construcción de estructuras XML, la integración con datos precedentes de fuentes no XML, y operaciones de manipulación y transformación de estructuras XML con LINQ To XML. Empecemos.


Nota: Los requisitos necesarios para probar LINQ To SQL son los que ya comentamos en el primer post de LINQ.


Creación y consulta de información en estructuras XML


Lo primero que vamos a ver es como de un modo sencillo podemos crear un sencillo documento XML (utilizando lo que LINQ To XML denomina Construcción Funcional, que permite crear un estructura XML, completa o no, en una única sentencia), y como también es realmente sencillo definir una consulta sobre dicha estructura (que no deja de ser un objeto en memoria fácilmente consultable con LINQ). El código necesario para crear el documento XML es el siguiente:







           XDocument DocXML= new XDocument(


                    new XDeclaration(«1.0″,»utf-8″,»yes»),


                    new XComment(«Ejemplo de doc. generado con LINQ To XML»),


                            new XElement(«Agenda»,


                                new XElement(«Contactos»,


                                    new XElement(«Contacto»,new XAttribute(«Categoría»,»Particular»),


                                    new XElement(«Nombre»,»José González»)),


                                    new XElement(«Contacto»,new XAttribute(«Categoría»,»Particular»),


                                    new XElement(«Nombre»,»María Méndez»)),


                                    new XElement(«Contacto»,new XAttribute(«Categoría»,»Trabajo»),


                                    new XElement(«Nombre»,»Antonio Muñoz»)))));


            Console.WriteLine(DocXML);


Con el código anterior, estamos creando un documento XML a partir de instanciar el objeto XDocument a través de una de las cuatro sobrecargas que tiene definidas. En concreto, la sobrecarga utilizada permite especificar mediante XDeclaration los valores utilizados para la versión, codificación y si el documento es standalone, y luego una lista de parámetros con los elementos que forman parte del documento XML y que en este caso son unos comentarios (especificados a través de XComment) y una jerarquía de elementos XML especificados mediante XElement. Finalmente, para visualizar por pantalla el documento vemos que no se necesita ningún tipo de factoría DOM, sino que es directamente visualizable sin hacer ningún tratamiento previo (lo que es una gran ventaja, ya que estamos creando un documento XML de un modo realmente sencillo con LINQ To XML). La salida por pantalla es la siguiente:



Al esquema anterior le podemos añadir de manera sencilla un nombre XML utilizando el objeto XNameSpace, que encapsula un espacio de nombres XML. Además, este documento no solo lo podemos visualizar de manera sencilla, sino que también guardarlo en disco realmente fácil utilizando el método Save() del objeto XDocument.







            XNamespace miEspacioNombres=»http://www.ciin.es/esquemas»;


            XDocument DocXML= new XDocument(


                    new XDeclaration(«1.0″,»utf-8″,»yes»),


                    new XComment(«Ejemplo de doc. generado con LINQ To XML»),


                            new XElement(miEspacioNombres + «Agenda»,


                                new XElement(miEspacioNombres + «Contactos»,


                                    new XElement(miEspacioNombres + «Contacto»,


                                        new XAttribute(«Categoría»,»Particular»),


                                    new XElement(miEspacioNombres + «Nombre»,»José González»)),


                                    new XElement(miEspacioNombres + «Contacto»,


                                         new XAttribute(«Categoría»,»Particular»),


                                    new XElement(miEspacioNombres + «Nombre»,»María Méndez»)),


                                    new XElement(miEspacioNombres + «Contacto»,


                                        new XAttribute(«Categoría»,»Trabajo»),


                                    new XElement(miEspacioNombres + «Nombre»,»Antonio Muñoz»)))));


 


            Console.WriteLine(DocXML.ToString());


            DocXML.Save(«Contactos.xml»);


 En este caso, el documento XML guardado en disco tendría este aspecto:



Nota: Igual que con el método Save() podemos guardar una estructura XML en disco, el método Load() de XDocument nos permite cargar un documento que está guardado en disco y manipularlo del mismo modo en que estamos trabajando con estructuras XML creadas en memoria.


Definir consultas sobre un documento XML como el anterior es bastante sencillo a través del uso de los operadores estándar de consulta. A modo de ejemplo, el siguiente código nos permite obtener todos los contactos de tipo Particular en el documento XML creado anteriormente:







         Console.WriteLine(«****Definiendo una consulta contra una estructura XML****»);


            var ContactosParticulares=


                from c in DocXML.Descendants(miEspacioNombres + «Contacto»)


                where (string)c.Attribute(«Categoría»)==»Particular»


                orderby (string)c.Element(miEspacioNombres + «Nombre»)


                select c;


            //Hacemos efectiva la consulta


            foreach (var contacto in ContactosParticulares)


            {


                Console.WriteLine(«{0}»,contacto);               


            }


 Del código anterior, hay que destacar un par de cosas:


·         EL método Descendant que nos permite obtener todos los elementos descendientes del tipo especificado (en este caso Contacto).


·         El hecho que tanto en la condición de la cláusula where como en la de la cláusula orderby estamos indicado que de manera específica se trabaja con elementos del documento XML de tipo cadena. Y la razón de este modo de trabajo hay que buscarla en el hecho de que LINQ To XML permite tratar los valores contenidos en elementos y atributos respetando sus tipos sin más que hacer un simple cast (aquí tenemos otra ventaja de LINTO To XML, frente al W3C DOM que siempre trata los nodos XML como textos).


La correspondiente salida por pantalla es la siguiente:



En esta salida vemos como efectivamente LINQ To XML no obliga a trabajar a nivel de documento XML completo, sino que podemos quedarnos a nivel de elementos XML.


Integración con datos no XML


Hasta ahora hemos visto como es bastante sencillo construir estructuras XML utilizando la Construcción Funcional de LINQ To XML. Sin embargo, las estructuras que hemos construido se basan en ir añadiendo de manera estática elementos  de tipo string al árbol. Como comentamos, se podrían añadir otros tipos de datos, y por supuesto, podemos utilizar variables en la construcción en lugar de valores. Pero, ¿Cómo hacemos más dinámica esta construcción funcional? La respuesta está de nuevo en utilizar el tipo IEnumerable<T> aplicada a objetos de tipo XElement, lo que nos permitirá construir estructuras XML a partir de objetos de memoria, resultados procedentes de hacer una consulta a una BD, etc. Para explicar este tipo de construcción, vamos a ver cómo podemos crear una estructura XML a partir de definir una consulta sobre un array de objetos de un cierto tipo:


·         Lo primero es definir la clase a partir de la cuál crearemos los objetos contenidos en el array.


 







        class Contacto


        {        


            public string Nombre;


            public string Telefono;          


        }


 


·         Lo siguiente que haremos es construir el arreglo de objetos aprovechándonos de algunas de las innovaciones en el lenguaje que ya vimos (en concreto, declaración implícita de tipos, tipos  anónimos e inicializadores de objetos):


 







            var contactos_agenda=new[]{


                new Contacto{Nombre=»José González»,Telefono=»11111″},


                new Contacto{Nombre=»Luis García», Telefono=»22222″},


              


            };


  


·         Sin más, ya podemos construir una estructura XML (en este caso no vamos a construir un documento completo, sino sólo una serie de elementos XML) a partir de definir una consulta XML sobre el array de objetos creado:







            XElement Contactos=


                new XElement(miEspacioNombres + «Contactos»,


                    from c in contactos_agenda


                    select new XElement(miEspacioNombres + «Contacto»,


                        new XElement(miEspacioNombres + «Nombre»,c.Nombre),


                        new XElement(miEspacioNombres + «Telefono»,c.Telefono)));


            Console.WriteLine(Contactos.ToString());


 La clave del código anterior está en que dentro de la cláusula Select podemos construir objetos de tipo XElement, y en que los objetos de tipo XElement pueden ser construidos a partir del resultado de una consulta a un objeto de memoria. La salida por pantalla generada es la siguiente:



Igual que hemos construido una estructura XML a partir de definir una consulta contra un objeto de memoria, se aplicaría la misma filosofía y método para generar estructuras / documentos XML a partir de datos de una BD relacional y apoyándonos en LINQ To SQL como ya vimos.


Operación y manipulación de estructuras XML


Como comentábamos, otra característica relevante de LINQ To XML es que permite realizar de un modo sencillo operaciones con estructura XML. Vamos a ver algunas de ellas:


·         Operaciones de Transformación, que nos permiten transformar y extender estructuras XML ya definidas. A partir de los elementos de una estructura XML existente, es posible crear nuevos elementos:


 







Console.WriteLine(«***********Operaciones de Transformación***********»);          


 XElement ElementoTransformado =


                new XElement (miEspacioNombres +  «Amigos»,


                    from c in ContactosParticulares


                    select new XElement(miEspacioNombres + «Amigo»,


                                new XAttribute(«NombreCompleto»,(string)c.Element(miEspacioNombres +  


                                                         «Nombre»))));


            Console.WriteLine(«nn {0}»,ElementoTransformado);


 


Lo que hemos hecho en el código anterior de la consulta de los contactos particulares del documento original XML para generar una estructura XML distinta, pero aprovechando datos de la consulta. La salida por pantalla obtenida es la siguiente:



 


·         Añadir, eliminar y actualizar elementos, LINQ To XML permite añadir, eliminar o modificar contenidos en una estructura XML a partir de los métodos Add(), Remove() y ReplaceContent() del objeto XElement. Así, para modificar la estructura anterior el código necesario sería:







            Console.WriteLine(«***********Añadir, Borrar, Modificar elementos**********»);


            XElement Telefono=new XElement(miEspacioNombres  + «Telefono»,»111111″);


            ElementoTransformado.Element(miEspacioNombres + «Amigo»).Add(Telefono);


            Console.WriteLine(«n Añadir»);


            Console.WriteLine(«n {0}»,ElementoTransformado);


           


            Console.WriteLine(«n Modificar»);


            ElementoTransformado.Element(miEspacioNombres  +


                               «Amigo»).Element(miEspacioNombres  +


                              «Telefono»).ReplaceContent(«22222»);


            Console.WriteLine(«n {0}»,ElementoTransformado);


 


            Console.WriteLine(«Borrar»);


            ElementoTransformado.Element(miEspacioNombres  +


                             «Amigo»).Element(miEspacioNombres  + «Telefono»).Remove();


            Console.WriteLine(«n {0}»,ElementoTransformado);


 Y la salida por pantalla correspondiente es la siguiente:



·         Navegar por los nodos de una estructura XML, LINQ To XML habilita varios mecanismos para navegar por los nodos de una estructura XML a través de los métodos Nodes() y Descendant() del objeto XElement. Estos métodos nos facilitan la tarea de poder trabajar con elementos concretos de nuestra estructura XML. Un ejemplo de uso de ambos es el siguiente:







            Console.WriteLine(«****Navegando por los nodos de un elemento XML****»);


            Console.WriteLine(«n Con Descendants()»);


            foreach(var x in ElementoTransformado.Descendants(miEspacioNombres +  «Amigo»)){


                Console.WriteLine(x);


            }


 


            Console.WriteLine(«n Con Nodes()»);


           foreach(var x in ElementoTransformado.Nodes()){


                Console.WriteLine(x);


            }


 La salida por pantalla que se obtiene es la siguiente:



Como vemos, la salida que producen ambos métodos es la misma para el caso de una estructura XML sencilla. Para estructuras más complicadas, el resultado será distinto puesto que Descendants() siempre nos devolverá todos los elementos por debajo de  un elemento especificado, mientras que Nodes() siempre nos va a devolver los nodos por debajo del primer elemento en la jerarquía de la estructura XML en cuestión.


Nota: En el caso de trabajar con atributos, sólo es posible realizar operaciones de borrado de los mismos a través de los método Remove() o SetAttribute() del objeto XAttribute.


Finalmente,  y para concluir el post os dejo una recopilación de las extensiones específicas de LINQ To XML que proporcionan la capacidad para realizar ciertas operaciones que tienen sentido al trabajar con estructuras XML (algunas de ellas las hemos visto en las operaciones de manipulación y transformación de estructuras XML).























Operadores


Descripción


Elements()


Devuelve todos los elementos hijos para cada XElement de una secuencia de XElements (IEnumerable <XElement>).


Descendants() y Ancestors()


Nos permiten consultar respectivamente los elementos situados por encima y por debajo (en la jerarquía) del elemento especificado (sin incluirlo).


Attributes()


Es una extensión llamada en IEnumerable<XElement> que devuelve una secuencia de atributos (IEnumerable<Attributes>).


ElementsBeforeThis, ElementsAftherThis


Permiten devolver todos los elementos hijos o contenidos que aparecen, respectivamente, antes y después de un elemento particular. En ambos casos devuelven un tipo IEnumerable<XElement>.


NodesBeforeThis, NodesAfterThis


Se comportan igual que ElementsBeforeThis y ElementsAfterThis, salvo que en este caso se devuelve un tipo IEnumerabe<XNode>.



Y hasta aquí lo que os quería contar de LINQ To XML. En la próxima me gustaría comentaros otras iniciativas que están surgiendo en torno a LINQ, y enseñaros con ejemplos de que va BLINQ. Espero que el post os haya resultado de interés. Como en el post anterior, os dejo aquí el zip con la solución del proyecto de LINQ To XML.

Integración de Reporting Services con SharePoint (I)


En este post vamos a explicar los pasos a seguir para la instalación y el uso de Microsoft SQL Server 2005 Reporting Services en sitios SharePoint, ya sean WSS 3.0 o MOSS. Esta configuración la conseguimos gracias a que Microsoft ha publicado recientemente la versión definitiva del Service Pack 2 de SQL Server que entre una de sus muchas novedades, habilita dentro de la configuración del Reporting Services una nueva opción para poderlo integrar con sitios SharePoint y a los también publicados Add-in de SQL Server 2005 Reporting Services para SharePoint, que nos incluyen:




  • Dos Web Part, Report Explorer y Report Viewer que nos permiten visualizar informes, exportarlos con otros formatos, imprimir y realizar zoom
  • Tres content types, Report Builder Model, Report Builder Report y Report Data Source con las que podemos diseñarnos informes desde el propio SharePoint conociendo el origen de los datos
  • Nuevas opciones en la administración central de SharePoint para administrar el acceso y la seguridad al servidor de Reporting Services

Antes de empezar a configurar el servidor de Reporting Services y el de SharePoint, tenemos que estar seguro de que nuestro escenario tengamos instalados todos estos componentes:




  • SQL Server 2005 Reporting Services
  • SQL Server 2005 Service Pack 2
  • Office SharePoint Server 2007 ó Windows SharePoint Services 3.0
  • Microsoft SQL Server 2005 Reporting Services Add-in for Microsoft SharePoint Technologies

Configuración del servidor de Reporting Services


Lo primero que vamos a realizar es la configuración del servidor de Reporting Services para poderlo integrar con sitios SharePoint. Para ello vamos a realizar los siguientes pasos:




  • Configurar los parámetros del IIS para definir un sitio web en el que alojar los Reporting Services

    • Accedemos a la administración del IIS
    • Normalmente, veremos que el Default Web Site está parado, ya que SharePoint utiliza el puerto 80 en un sitio web diferente y deshabilita este sitio, por eso vamos modificarle especificando un puerto diferente, ej. 8080, y después lo habilitamos para que así esté disponible en la configuración de Reporting Services




  • Vamos a la pantalla de administración de Reporting Services pulsando sobre Start à All Programs à Microsoft SQL 2005 à Configuration Tools à Reporting Services Configurations
  • El primer paso que realizamos para la configuración es entrar en la opción de Report Server Virtual Directory y crear un nuevo directorio virtual en nuestro sitio web, seleccionando el Default Web Site y estableciendo un nombre del directorio virtual, por defecto ReportServer

 



  • Después pulsamos sobre la opción Database Setup para crear una nueva base de datos de Reporting Services y la vinculamos con nuestro sitio SharePoint realizando los siguientes pasos:

    • Seleccionamos el servidor SQL Server donde queramos guardar la base de datos
    • Pulsamos sobre New para crear esta base de datos, especificamos las credenciales con permisos necesarios en el SQL Server para crear bases de datos y establecemos un nombre a la base de datos, ReportServer
    • Por último seleccionamos la opción Create the report server database in SharePoint integrated mode para integrarlo con SharePoint y pulsamos Ok.




    • Nos aparecerá una pantalla en la que volveremos a especificar las credenciales de un usuario con privilegios administrativos que el servidor usará para acceder a la base de datos.

  • Por último, pulsamos sobre Web Service Identity y seleccionamos el application pool que queremos que tenga asociado nuestro sitio web ReportServer.



Ahora que ya tenemos configurado todos los parámetros del Reporting Services, pulsamos sobre la opción de SharePoint Integration y vemos que aparece un enlace que nos abrirá la administración central de SharePoint para poder seguir con la configuración.



Nota: Una consideración a tener en cuenta, es que si instalamos el servidor de Reporting Services en una maquina diferente a donde tenemos instalado SharePoint, tendremos que realizar una instalación básica de SharePoint en este servidor para que tenga disponible el modelo de objetos de SharePoint.


Configuración del servidor SharePoint


Una vez que ya tenemos configurado nuestro servidor de Reporting Services y después de ejecutar el programa de instalación de Microsoft SQL Server 2005 Reporting Services Add-in para Microsoft SharePoint Technologies en el servidor de SharePoint, vamos a configurar nuestro servidor SharePoint. Para ello, si vamos a la administración central de SharePoint y pulsamos sobre la ficha de Application Management podremos ver el nuevo menú de Reporting Services que nos ha añadido estos add-in.



Este menú está compuesto por tres apartados diferentes que tendremos que configurar uno a uno para terminar de configurar nuestro servidor, y que sirven para:



  • Manage integration settings: Configurar la URL de acceso al servidor de Reporting Services (ej. http://ServerName:8080/reportserver) y su modo de auntentificación (Windows o trusted account)



  • Grant database Access: Configurar el nombre del servidor donde se encuentra la base de datos de Reporting services y las credenciales necesarias para poder acceder a ella.




  • Set Server defaults: Modificar otros parámetros de la configuración como los timeout de acceso, habilitar los logs, …


Una vez que hayamos terminado de realizar todos estos pasos, ya tendremos configurado nuestro servidor SharePoint para poder utilizar Reporting Services.


En los próximos post veremos cómo podemos desarrollar informes y trabajar con ellos desde SharePoint.


Links de descargas:

















Microsoft SQL Server 2005 Reporting Services Add-in para Microsoft SharePoint Technologies


Microsoft SQL Server 2005 SP 2


Descarga


Descarga


Novedades


Novedades

Configuración de SharePoint con varios modelos de autentificación

Vamos a explicar los pasos que hay que seguir para configurar dentro de en un mismo sitio SharePoint diferentes formas de autentificación, ya que es una situación típica en escenarios de intranet/internet, que necesitan autentificación Windows (mediante directorio activo o cuentas locales) para su zona intranet y para internet, habilitar el acceso anónimo y la autentificación basada en formularios, que la conseguimos gracias a los Membership Provider que nos proporciona ASP.Net 2.0.

Para poder configurar todo nuestro escenario, vamos a realizar los siguientes pasos:

  1. Creación de dos Web Applications, una para cada modelo de autentificación
  2. Generación de una base de datos en un servidor SQL para almacenar las cuentas de usuarios y roles.
  3. Cambiar el modo de autentificación de la Web Application correspondiente a la zona internet para que disponga de acceso anónimo y poder validarse con los usuarios creados en la base de datos.

Creación del sitio intranet à http://wssgob.com

Una vez que hayamos terminado de instalar y configurar Windows SharePoint Services v3 (ó la versión de MOSS) en nuestro entorno de servidores, vamos a crear una nueva Web Application con su correspondiente colección de sitios. Para ello desde el sitio de la Administración Central de SharePoint, seleccionamos en la ficha de Application Management la opción de Create or Extend Web Application, pulsamos sobre Create a New Web Application y rellenamos el formulario con los siguientes parámetros:

·         Descripción: wssgob.com

·         Puerto: 80

·         Authentication Provider: NTLM

·         Acceso Anónimo: No

·         Por último los correspondiente parámetros de acceso al servidor SQL para alojar la base de datos de contenido del sitio

Después de crear la Web application, crearemos una colección de sitios asociada a esta Web application, entrando en la ficha de Application Management pulsamos la opción Create Site Collection y después la configuramos con los siguientes datos:

·         Web Application: http://wssgob.com

·         Título: CIIN

·         Plantilla: Team Site

 

Hasta ahora lo que hemos realizado es una web application con su correspondiente colección de sitios, http://wssgob, configurada mediante autentificación Windows y cuentas de directorio activo, que es la que SharePoint utiliza por defecto.

     

Creación del sitio internet à http://internet.wssgob.com

Después de comprobar que nuestro sitio intranet funciona correctamente, vamos a extender la web application a otro sitio web del IIS, que será el que configuremos con acceso anónimo y la autentificación basada en formularios.

Para extender la Web Application de la intranet a un nuevo sitio web del IIS, tenemos que ir a la Administración Central de SharePoint, seleccionar en la ficha de Application Management la opción de Create or Extend Web Application, pulsamos sobre Extend an Existing Web Application. Al rellenar estos datos tenemos que prestar atención a la web application que estemos seleccionando, ya que tiene que ser la correspondiente a la de la zona intranet que hemos creado en el apartado anterior.

·         Web Application: http://wssgob.com

·         Puerto: 80

·         Host Header: internet.wssgob.com

·         Authentication Provider: NTLM

·         Acceso Anónimo: No

·         Zona: Internet

     

Creación de la base de datos para almacenar los usuarios

El siguiente paso para la configuración de nuestro escenario con dos formas de autentificación, siendo una de ellas la basada en formularios, es la creación de la base de datos que aloje la información de los usuarios y roles que generemos. Microsoft proporciona un comando aspnet_regsql.exe que está disponible con la instalación del .Net framework 2.0, que realiza todo el trabajo de creación de las tablas necesarias en un servidor SQL. En este paso podríamos crearnos una base de datos personalizada, en un servidor de bases de datos diferentes, etc… El procedimiento para crearla lo podemos ver en este Link.

Para crear la base de datos con este comando seguimos los siguientes pasos:

  1. Abrir una consola de símbolo de sistema y acceder a la carpeta de %windir%Microsoft.NETFrameworkv2.0.5027
  2. Ejecutar el siguiente comando aspnet_regsql –A all –E

Descripción de los parámetros:

-A all: Especifica que queremos instalar la feature de usuarios y roles

-E: Indica que usaremos autentificación Windows para conectar al SQL Server

Para ver el resto de parámetros que se pueden utilizar ejecutar aspnet_regsql /?

  1. Este comando nos creará la base de datos aspnetdb en el servidor SQL local en donde lo ejecutemos.

Una vez que tengamos creada la base de datos tendremos que crear los primeros usuarios y roles, para ello existen varios métodos. El primero sería terminar de configurar nuestro sitio SharePoint para con los dos modelos de autentificación y después crear una página aspx dentro de la administración central con SharePoint Designer y agregando un control de ASP.Net para crear usuarios en base de datos,


El otro método es realizarlo desde Visual Studio, creando un nuevo sitio web y configurar el web.config para que acceda a la base de datos del servidor SQL y así, desde la opción de ASP.Net Configuration del menú de Visual Studio acceder al sitio web de la administración de ASP.Net y poder crear los usuarios y roles.

  

Habilitar la autentificación basada en formularios a Web Applications de la zona internet

Una vez que ya tenemos creadas las dos web applications y la base de datos que va a almacenar a los usuarios y roles, necesitamos modificar el fichero de configuración web.config del sitio de la zona internet y el de la administración central de SharePoint para que pueda acceder a la base de datos y poder utilizar la autentificación basada en formularios.

Editamos el web.config de la web application que queremos que tenga este método de autentificación de la siguiente manera:

  • Crearemos una copia de seguridad del fichero de configuración, web.config
  • Abrimos el fichero y debajo de la etiqueta de </SharePoint> añadimos la cadena de conexión de la base de datos:


<connectionStrings>

<add name=»AspNetSqlProvider» connectionString=»server=NombreSqlServer; database=aspnetdb; Trusted_Connection=True» />

</connectionStrings>

  • Después, dentro de la etiqueta <system.web>, ponemos el código para configurar la creación de los usuario y roles:


<membership defaultProvider=»AspNetSqlMembershipProvider»>

<providers>

<remove name=»AspNetSqlMembershipProvider» />

    <add connectionStringName=»AspNetSqlProvider»

passwordAttemptWindow=»10″

enablePasswordRetrieval=»false»

enablePasswordReset=»true»

requiresQuestionAndAnswer=»true»

applicationName=»/»

requiresUniqueEmail=»false»

passwordFormat=»Hashed»

description=»Almacena los usuarios y roles en la base de datos de SQL Server»

name=»AspNetSqlMembershipProvider»

Type=»System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a» />

</providers>

</membership>

       

<roleManager enabled=»true» defaultProvider=»AspNetSqlRoleProvider»>

<providers>

<remove name=»AspNetSqlRoleProvider» />

<add connectionStringName=»AspNetSqlProvider»

applicationName=»/»

description=» Almacena los usuarios y roles en la base de datos de SQL Server»

name=»AspNetSqlRoleProvider»

type=»System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a» />

</providers>

</roleManager>

  • Al acabar, guardamos el fichero y lo cerramos

Una vez que ya hemos modificado el web.config de la web application que queremos que tenga la validación por formularios, debemos realizar estos mismos pasos en el fichero de configuración web.config del sitio donde está alojado la administración central de SharePoint, modificando solamente la línea donde se especifica el administrador de los roles:


<roleManager enabled=»true» defaultProvider=»AspNetWindowsTokenRoleProvider»>

Después de realizar estas modificaciones a los ficheros de configuración, tan solo queda cambiar los parámetros de autentificación la web application del sitio de la zona de internet para que utilice la autentificación basada en formularios que acabamos de crear.

Para realizarlo, abrimos la administración central de SharePoint, y en la pestaña de Application Management pulsamos sobre Authentication Provider. Lo primero que debemos hacer es seleccionar la web application correcta en la parte superior, y como observamos en la imagen están la dos zona configuradas mediante autentificación Windows:

 

Cuando ya hemos seleccionado la Web Application correcta, pulsamos sobre la zona internet y modificamos los parámetros de la autentificación que está utilizando, cambiándolo para que tenga autentificación basada en formularios y permitiendo el acceso anónimo:

·         Tipo de autentificación: Forms

·         Acceso Anónimo: Si

·         Membership Provider Name: AspNetSqlMembershipProvider

·         Role Manager Name: AspNetSqlRoleProvider

Los valores de Membership Provider y de Role Manager son los que hemos definido en los ficheros de configuración web.config en el paso anterior.

 

Al acabar de realizar todos estos pasos habremos finalizado la configuración de nuestro sitio SharePoint para que disponga dos formas diferentes de acceso, cada una de ellas con un método de autentificación diferente:

·         http://wssgob.com, autentificación Windows (Cuentas de usuario de directorio Activo o cuentas locales)

·         http://internet.wssgob.com, Acceso anónimo o autentificación basada en formularios

BTS 2006: Como manejar estructuras repetitivas en el esquema origen y en el esquema destino de un mapa de BizTalk

En
un post  anterior 
explicamos cómo crear estructuras repetitivas en el esquema destino de
un mapa de BizTalk
Server 2006
(BTS 2006). Ahora bien, ¿Cómo tratamos aquellos casos en los
esquema origen también tenemos algún componente repetitivo? Una primera implicación
es que debemos de ser capaces de poder extraer los datos repetitivos, para a
continuación procesaros en el mapa de BTS 2006 con un Functoid
especial (de tipo table lopping como hacíamos en el ejemplo explicado en post
previo
).  El proceso de extracción de
datos repetitivos de un esquema origen pude parecer a  priori bastante complicado. Sin embargo, en
este post vamos a ver qué utilizando las herramientas que nos proporciona BTS
2006, resulta bastante sencillo.  Lo
primero que haremos será crear el esquema origen (con componentes repetitivas)
utilizando la herramienta Flat
File Schema Wizard
de BTS 2006, para a continuación definir el
correspondiente mapa de BizTalk, que es capaz de leer estructuras repetitivas
en origen y generar estructuras repetitivas en destino.

Creación
del esquema origen con el Flat File Schema Wizard

Como
ya habíamos comentado en el post anterior el editor de mapas de BizTalk
necesita dos esquemas XSD, uno como origen y otro como destino. Podemos
trabajar con esquemas ya creados pero en este caso vamos a crear el esquema
origen a partir de un documento txt que contiene varias líneas de producto con
la misma estructura, como se muestra en la siguiente figura.

Figura 1: archivo txt del que se
obtendrá el esquema origen para el mapa de Biztalk

 

Para  poder obtener un esquema XDS de un fichero
plano es necesario utilizar el Flat File Schema Wizard  que nos proporciona BizTalk. Para utilizar
esta herramienta, añadimos un nuevo elemento al proyecto de BizTalk de tipo Schema
Files -> Flat File Schema Wizard
.

 
Figura 2: Menú de Biztalk para añadir
un esquema al proyecto.

Lo
primero que tenemos que hacer es seleccionar el fichero a partir de cual
queremos obtener el esquema XSD, definir un nombre para el elemento raíz del
esquema y un namespace para el mismo.

 
Figura 3: Menú de Biztalk para definir
las propiedades del esquema.

 

Una
vez realizado esto pinchamos en Next,
y en la nueva pantalla se nos indicará que seleccionemos aquella porción del
fichero plano que queremos que sea englobada en el a generar. Además, tenemos
que comprobar que las líneas repetitivas tienen la misma longitud y terminan
con la misma secuencia, en nuestro caso será un salto de línea, como se puede
ver en la siguiente figura.

 
Figura 4: Menú de Biztalk para
comprobar el texto que se va analizar a realizar el esquema XSD. Las líneas
repetidas tienen que tener la misma longitud y empezar y terminar con algún
carácter o secuencia determinada.

Nota:
En caso de que las líneas repetitivas no tuvieran la misma estructura
deberíamos modificarlas en el fichero plano hasta conseguir que sean idénticas
(y se comporten de esta manera como auténticas repeticiones).

En
la siguiente ventana simplemente nos indica cómo queremos identificar las
componentes, existe la posibilidad de realizarlo de dos maneras distintas:

  • Por posiciones relativas.
  • Utilizando un delimitador.

En nuestro caso utilizaremos la opción By delimiter Symbol. Damos a next, en la siguiente ventana podemos especificar a través de que
símbolo queremos identificar los elementos. 
En nuestro caso escogemos la opción {CR}{LF}, como muestra la siguiente
figura.  La opción que hemos escogido nos
indica que utilizaremos el salto de línea para separar los elementos del
fichero plano.

 
Figura 5: Menú de Biztalk para
seleccionar el símbolo que nos va a separar a los elementos del fichero plano.

Cuando
hayamos escogido esto damos a next
para ir al siguiente paso que, para el tipo de estructuras que estamos
analizando, es el más importante. En la nueva ventana, tenemos los elementos
del fichero plano que hemos seleccionado. Lo primero que podemos hacer es
darles a cada uno algún nombre identificativo, ya que por defecto nos genera
unos nombres automáticos poco explicativos. Cuando hayamos puesto nombre a los
elementos, nuestro siguiente paso es escoger el tipo de elemento que será en
nuestro esquema. Existen cuatro tipos:

  • Field Element
  • Field Atribute
  • Record
  • Repeating Record

Las
dos primeras se utilizan para componentes que no tienen ninguna estructura
interna, es decir, que no tenemos que “trocearlo más”. La elección entre una u
otra ya depende de las necesidades de cada caso. Los otros dos tipos se
utilizan para estructuras que tienen subelementos, resaltando que el ultimo
tipo aparte de tener subelementos, se puede repetir el nodo un cierto número de
veces. Para el tipo de datos y análisis en el mapa de BizTalk que queremos
hacer, debemos de escoger este ultimo tipo.

Una
duda que se nos puede plantear, es que tenemos un cierto número de líneas con
la misma estructura, por lo tanto debemos de analizar cada línea o por el
contrario con analizar una de las líneas será suficiente. La respuesta está en
la segunda opción, pero tenemos que decir a BizTalk de alguna manera que solo
analice una de las líneas para poder de esta manera procesar ficheros con un
número cierto número de líneas (desconocido a priori) con idéntica estructura.
Para hacer esto el wizard que estamos utilizando nos da la opción de que
después de seleccionar un Repeating Record podemos ignorar las líneas
siguientes del archivo, sin más que seleccionar la opción Ignore que se encuentra en la lista desplegable del tipo de
elemento. Para este ejemplo hemos configurado los elementos como se observa en
la siguiente figura.

 
Figura 6: Menú de Biztalk para
seleccionar el tipo de componente extraídos del fichero plano.

 
También
podemos seleccionar el tipo de dato, en nuestro caso como se puede observar en
la figura anterior que todos los elementos se han configurado como string. Una vez que ya hemos
especificado que en el fichero origen hay estructuras repetitivas,  tenemos que configurar como BTS 2006 va a
tratar cada línea de este tipo. Damos 2 veces a next, en la ventana que nos aparece comprobamos que este
seleccionada solo la primera línea repetitiva y volvemos a dar a next.

 
Figura 7: Menú de Biztalk para
seleccionar el elemento que se va a analizar.

 

 

 
Figura 8: Menú de Biztalk para ver la
línea de texto que se va a analizar.

Como
en el caso anterior tenemos que seleccionar como queremos “trocear” la línea
repetitiva, en este caso ya que el texto viene totalmente unido, si ningún
carácter de separación escogeremos la opción de By relative positions, y volvamos a dar a next. Además, tendremos que indicar cuál es el carácter de comienzo
de cada línea repetitivita para que el troceado sea correcto. En nuestro caso
hemos escogido como identificador un espacio en blanco (el identificador
utilizado depende de cómo nos entreguen el fichero plano origen) antes de la
línea. Además “troceamos” el texto como se muestra en la siguiente figura (por
posiciones relativas)  y damos a next.

 
Figura 9: Menú de Biztalk para
seleccionar el tipo de identificador y para trocear el texto del fichero plano.

Nos vuelve a salir una ventana muy parecida a la de la
figura 5, pero en  este caso con menos
elementos (pues ahora estamos tratando únicamente los elementos que forman
parte de una línea repetitiva). Volvemos a repetir el mismo proceso que antes,
es decir, les ponemos nombre y elegimos el tipo de componente.

 
Figura 10: Menú de Biztalk para
seleccionar el tipo de componente extraídos del fichero plano.

Finalmente
damos a next y después a finish, así ya tendremos configurado el
esquema de origen. Para poder realizar el mapa nos hace falta un esquema de
destino, en nuestro caso le hemos construido utilizando el editor de BizTalk.
Simplemente mostramos cual es su estructura en la siguiente figura.

 
Figura 11: Esquema destino XSD que se
utilizara en el mapa de Biztalk.

Añadimos
un mapa al proyecto del mismo modo que hicimos en el post
anterior
, seleccionamos los esquemas de origen (el que hemos obtenido con
el wizard) y de destino (el que hemos construido manualmente).

 

 
Figura 12: Imagen del mapa antes de
unir los campos.

Como
vemos en la figura anterior el esquema destino es muy parecido el esquema
origen. Las únicas diferencias es que en el esquema destino se han eliminado
los elementos vendedor, direccion  y desc_producto.
Pero esto no es importante ya que la finalidad de este post es explicar cómo
trabajar con estructuras iterativas tanto en el origen y en la salida. Por lo
tanto hay ciertos campos que les uniremos directamente (ver mapa final). Sin
embargo realizaremos un estudio más particular para trasladar los valores del
loop de origen al loop de destino.

En
un primer momento se nos puede plantear la duda de que siguiendo la filosofía
que utilizando los FunctoIds table looping y table extractor necesitaríamos conocer el número de
elementos repetitivas con las que vamos a trabajar, sin embargo vamos a ver
como esto no es así. Simplemente tenemos que insertar en el mapa un table
looping
y dos table extractor ya que solo queremos
extraer dos campos y por lo tanto tendremos dos columnas en la tabla de
configuración del table looping.  Como
hemos mencionado antes, en principio haría falta saber el número de
repeticiones para configurar de manera adecuada las entradas de la función table
looping
, pero en la práctica esto no es así, sino que solamente con
unir los campos que cuelgan del repeating record con el table looping, este es
capaz de saber que pertenecen a un repeating record y completar
dinámicamente y en tiempo de ejecución la tabla de configuración a partir del
número de repeticiones que integran la estructura repetitiva en origen.

Como
explique en el post
anterior
para configurar un table
looping
primero hay que enlazarle con un elemento padre, en nuestro
caso  utilizaremos el nodo raíz del
esquema origen PEDIDO. Unimos los
elementos id_linea y id_producto  que cuelgan del nodo pro_loop. Posteriormente
seleccionamos el table looping  damos botón derecho – Configure Functoid inputs, e insertamos una constante con valor 2,
que corresponde con el número de columnas.

 

 
Figura 13: Pantalla de configuración
de los parámetros de entrada del table looping.

Antes
de pasar a configurar los  debemos de
configurar la tabla del table looping, para ello la seleccionamos, damos botón
derecho y seleccionamos Configure Table Looping Grid, nos
aparecerá una ventana parecida a la siguiente imagen:

 

 
Figura 14: Pantalla de configuración
de la del table looping.

Como
observamos solo tenemos una fila, esto es debido a que el table looping reconoce dinámicamente cuantas veces se tiene que
repetir ya que los parámetros de entrada cuelgan de un repeating record, por lo tanto esto nos facilita el trabajo mucho.

Para
extraer los datos del table looping  conectamos en cascada dos funciones de tipo table
extractor
. Como comentamos, el primero de ellos obtendrá los valores de
la primera columna del table looping, mientras que el
segundo obtendrá los valores de la segunda columna. Para configurar cada table
extractor
, hacemos clic sobre uno de ellos con botón derecho del ratón
y seleccionamos Configure Functoid inputs. Insertamos una constante con el
valor de la columna que queremos extraer, como se observa en la siguiente
figura.

 
Figura 15: Pantalla de configuración
de parámetros de entrada del table extractor.

Para
el otro table extractor le
configuramos igual pero la constante tendrá valor 2. Unimos cada table extractor con el componente del
esquema destino que corresponda. Finalmente unimos el table looping con el nodo raíz del esquema destino, que en nuestro
caso es root, de esta forma ya
tendremos configurado el mapa para poder trabajar con elementos repetitivos
tanto en el esquema origen como en el destino.

 
Figura 16: imagen del mapa final.

 

Seguramente
encontraréis que es mecanismo de tratamiento de estructuras repetitivas en
origen y destino es realmente útil para muchos procesos de negocio que utilizan
las empresas hoy en día.  Un ejemplo
claro de aplicación que hemos probado en el CIIN
es el ciclo de facturación, en el que en un fichero plano origen se especifican
los datos de facturación de un pedido con una serie de líneas de pedido (y que
son las estructuras repetitivas) que tiene que ser enviado en formato EDI a un
cierto destino (el estándar EDI es uno de los ejemplos más claro de documento
que presenta múltiples estructuras repetitivas).

Sin
más, crearíamos la correspondiente orquestación de BTS 2006 que se encargue de
hacer efectiva la transformación definida en el mapa creado, definiríamos los
correspondientes
puertos físicos de recepción y envío
.

 
Figura 17: imagen de la orquestación
de la solución.

Al
probar la solución se generaría el fichero XML esperado.

 

 
Figura 18: fichero XML generado por
Biztalk server 2006.

 
Hasta
aquí ha llegado mi segundo post en geeks, espero que os sea de utilidad. Un
saludo a todos (Os dejo el msi de la aplicación de BTS por si alguno lo queréis
probar).

 

Anuar
Khan Ali Franco


 

LINQ To SQL: Definiendo un Modelo de Entidades y operando con él!

Después de unos días ajetreados preparando el seminario sobre LINQ que próximamente impartiremos en el CIIN (de hecho justo hoy hemos acabado de hacer las últimas pruebas con LINQ To XML), en este post os voy a comentar algunos aspectos interesantes que hemos aprendido sobre LINQ To SQL. Pero, ¿Qué es LINQ To SQL? Según la documentación disponible en el sitio oficial del proyecto de LINQ, LINQ To SQL es el componente específico de LINQ que proporciona la infraestructura de runtime necesaria para utilizar datos relacionales como objetos y poder definir consultas sobre dichos objetos, es decir, habilita la consulta de contenedores de datos relacionales sin tener que abandonar la sintaxis o el entorno de tiempo de compilación.  Para hacer posible esto, LINQ To SQL se apoya en las siguientes características clave:


·         Las innovaciones del lenguaje (C# 3.0 y VB 9.0) que vimos en un post previo, así como en las características propias de LINQ (consultas integradas en el lenguaje).


·         Mapping del esquema de la BD en clases, propiedades, métodos, etc. De hecho, la correspondencia que LINQ To SQL hace entre los elementos de un esquema de una BD y los elementos correspondientes a nivel del CLR es el siguiente:



·         Persistencia, que habilita el control automático de cambios en la BD, y la actualización de datos a través de sentencias T-SQL.


·         Integración de la información del esquema de la BD en metadatos del CLR:las tablas se tratan como colecciones, los datos (columnas) son descritos en clases, la conexión a la BD y los resultados están tipados, etc. Cómo se comenta en el artículo El Proyecto LINQ (traducción de Octavio Hernández), esta integración es la que permite compilar las definiciones de tablas y vistas SQL en tipos del CLR para que puedan ser accedidas desde cualquier lenguaje.


En la práctica, LINQ To SQL  provee de los mecanismos necesarios para encapsular y utilizar en nuestras aplicaciones la definición (completa o no) de un cierto esquema relacional, y poder definir consultas LINQ sobre las clases mapeadas. El esquema de la arquitectura de LINQ To SQL nos permitirá comprender mejor las ideas expuestas:



Como se puede deducir, los puntos clave de la arquitectura de LINQ To SQL son los siguientes:



  • Consultas a nivel de aplicación integradas en el lenguaje.


    • Base de datos tipada en objetos: mapeo a través de atributos, devuelve objetos al realizar consultas SQL.

  • LINQ To SQL garantiza la persistencia, control de cambios y control de concurrencia.


    • LINQ To SQL traduce las consultas en SQL que se envía a la BD: las consultas no se ejecutan en memoria y  no se transmiten como IL al servidor BD.

Bueno, después de esta introducción “tan teórica”, es el momento de pasar a la acción. En los siguientes apartados trataré de cubrir aspectos relativos al mapeado de objetos de acuerdo a un esquema relacional existente, y como definir operaciones sobre dichos objetos  que luego se traducirán en la BD a las correspondientes sentencias T-SQL. Empecemos.


Nota: Los requisitos necesarios para probar LINQ To SQL son los que ya comentamos en el primer post de LINQ.


Creación del modelo de objetos


El modelo de objetos creado se basa en una sencilla base de datos formada por dos tablas relacionadas: Md_Clientes y Md_Vehiculos.


Como hemos comentado, el mapeo de elementos de la BD se basa en definir las clases, propiedades y métodos necesarios para poder interactuar con ellos del mismo modo que con otros tipos del CLR. LINQ To SQL nos da tres posibilidades para realizar este mapeo:


(i)                  Utilizando la herramienta SQL Metal la cuál a partir del archivo .mdf de nuestra BD genera un archivo .cs con todos los elementos de la BD mapeados.


Utilizando LINQ To SQL OR Designer que es integrado en VS 2005 después de instalar el LINQ Preview. En este caso, el mapeo es todavía más sencillo puesto que sólo tenemos que arrastrar las tablas que nos interesen de nuestro esquema  de datos a la superficie de diseño (seguro que esto le suena a más de uno). Si nos vamos a la vista de código (después de grabar :P), veremos que se nos han generado todos los elementos de la BD que hemos situado en la superficie de diseño.



(i)                  Hacer el mapeo “a mano”, es decir, definir adecuadamente en código las clases y propiedades que representan las tablas y campos a mapear.


¿Qué método es más adecuado para definir el modelo de objetos? Pues básicamente, y desde mi punto de vista, está claro que las opciones (i) e (ii) son más rápidas, pues hacen todo el trabajo por nosotros (aparte de que no se dejan cosas en el tintero) y nos mapean todas las tablas con sus columnas en un archivo .cs. Sin embargo, perdemos un poco de flexibilidad puesto que el modelo de objetos se define de acuerdo a la descripción fiel de la BD y no con respecto a necesidades de negocio concretas. En cambio, el método (iii) es más flexible ya que nos permite definir “entidades” de acuerdo al proceso de negocio concreto, de manera que nuestras clases estarán formadas por aquellas propiedades que realmente son interesantes para nosotros (y no por todas las columnas de la correspondiente tabla en la BD). Evidentemente el método (iii) implica más esfuerzo de desarrollo y conocer en mayor detalle como mapear tablas en objetos en LINQ. Nosotros vamos a ir por la opción (iii) para tener un mayor control de lo que estamos haciendo.


Dentro del proyecto de LINQ que hayamos creado, vamos a definir una clase que represente a la entidad Clientes. La definición de esta clase la vamos a basar en la tabla Md_Clientes de nuestro esquema relaciona.  Esta clase es una representación lógica y tipada de la tabla en la BD física, y actúa a todos los efectos como un proxy para realizar consultas fuertemente tipadas.







     [Table(Name=»Md_Clientes»)]


        public class Clientes


        {


            [Column (Id=true, DBType=»varchar»)]


            public string ID_Cliente;


 


            private string _NombreCliente;


            [Column(Storage=»_NombreCliente», DBType=»varchar»)]


            public string NombreCliente


            {


 


                get {return this._NombreCliente;}


                set {this._NombreCliente=value;}


 


            }


 


            private string _CiudadCliente;


            [Column(Storage=»_CiudadCliente», DBType=»varchar»)]


            public string CiudadCliente


            {


 


                get {return this._CiudadCliente;}


                set {this._CiudadCliente=value;}


 


            }


 


        }


Como vemos en el código anterior, el mapeo de la entidad Clientes a la tabla Md_Clientes  se basa en utilizar los atributos adecuados:


·         El atributo Table que define cuál es la tabla de la BD con la que se encuentra vinculada la entidad Clientes. Este atributo prese


·         El atributo Column que permite especificar la correspondencia de los miembros de la clase con las correspondientes columnas de la tabla física. Además, este atributo lleva asociados una serie de parámetros que nos permiten especificar si el miembro de la clase se corresponde con la primary key de la tabla de la BD (parámetro Id), el tipo de dato parámetro (DBType, si el campo es autogenerado (parámetro autogen), etc.


Una vez que hemos creado la primera entidad del modelo de objetos, nos falta definir el canal que nos permita traer objetos desde la BD y enviar cambios a la BD. Este canal es lo que en LINQ se conoce como DataContext, que se encarga de traducir las peticiones de objetos en consultas SQL (ver el esquema de la arquitectura de LINQ) y devolver los resultados como objetos. El objeto DataContext se puede especificar de dos formas:


·         Definiendo una instancia del mismo y pasando como parámetro la cadena de conexión de la BD. Este método implica que para definir consultas contra la BD tengamos que utilizar el método GetTable.







DataContext BD=new DataContext(@»C:Program FilesLINQ PreviewDataCLIENTES.mdf»);


Table<Clientes> Customers=BD.GetTable<Clientes>();


Nota: Como vemos, como cadena de conexión estamos especificando un path físico en el que tenemos almacenada la BD. DataContext admite otras tres sobrecargas (una de ellas es la típica cadena de conexión de ADO.NET)  en la definición de la cadena de conexión.


·         Definiendo el objeto DataContext de modo fuertemente tipado, lo que facilita la definición de las consultas en la BD puesto que no necesitamos utilizar GetTable tras crear una instancia de dicho objeto. Esta es la opción que vamos a utilizar. Para ello, tenemos que crear una clase que herede de DataContext.







    class Program


    {


        //BD CLIENTES


        public class CLIENTES: DataContext


        {


            public Table<Clientes> Customers;


            public CLIENTES(string connection): base(connection){}


        }


Una vez que hemos definido la primera entidad del modelo y tipado el correspondiente DataContext, ya podemos empezar a realizar consultas contra el modelo de objetos.


Realizando consultas contra el modelo de objetos definido


La filosofía de la realización de consultas es la que ya vimos cuando se definen consultas sobre objetos de memoria.







//Conexión Fuertemente Tipada


CLIENTES BD2=new CLIENTES(@»C:Program FilesLINQ PreviewDataCLIENTES.mdf»);


//Clientes de Madrid


BD2.Log=Console.Out;


            var MisClientes =


                    from c in BD2.Customers


                    where c.CiudadCliente==»Madrid»


                    select c;


            Console.WriteLine(«*********************Consulta estándar*********************»);


            foreach (var cliente in MisClientes)


            {


                Console.WriteLine(«ID: {0},Nombre: {1},Ciudad: {2}»,


                        cliente.ID_Cliente,cliente.NombreCliente,cliente.CiudadCliente);


            }


            Console.ReadLine();


La salida por pantalla que se obtiene nos muestra la sentencia T-SQL que LINQ To SQL envía a la BD (esta sentencia se captura mediante BD2.Log=Console.Out) y el resultado de la consulta, que se ejecuta en el momento en que se define la correspondiente iteración.




Vamos a definir ahora una consulta más compleja que permita incluir el uso de Joins a partir de que conocemos que Md_Clientes y Md_Vehicles están relacionadas mediante a través del campo ID_Cliente. Antes de realizar la consulta, tendríamos que definir una clase Vehiculos que realice el mapeo correspondiente con la tabla Md_Vehiculos. La filosofía es la misma explicada para la clase Clientes, por lo que no os reproduzco el código. Si nos centramos en la definición de la consulta, supongamos que queremos obtener para cada cliente de la BD la marca de su coche, tendríamos que definir una consulta como la siguiente:







            var ClientesMarcaCoche=


                from c in BD2.Customers


                join ve in BD2.Vehicles on


                c.ID_Cliente equals ve.ID_Cliente


                orderby c.CiudadCliente ascending


                select new{NIF=c.ID_Cliente,


                    Nombre=c.NombreCliente,Coche=ve.MarcaVehiculo};


            Console.WriteLine(«*********************Consulta con joins*********************»);


            Console.WriteLine(«n»);


            foreach(var cliente in ClientesMarcaCoche)


            {


                Console.WriteLine(«NIF: {0}, Nombre: {1},Coche: {2} «,


                    cliente.NIF,cliente.Nombre,cliente.Coche);


            }


            Console.ReadLine();


Aparte del uso del join, en la consulta anterior es destacable el uso del operador orderby para obtener los resultados ordenados según un cierto criterio y el hecho de que en lugar de devolver un objeto completo como hicimos en la consulta anterior estemos devolviendo una colección arbitraria utilizando para ello un tipo anónimo. La salida que se obtiene por pantalla es la siguiente:



Hasta ahora hemos visto como definir un modelo de objetos a partir del esquema de una BD y como realizar consultas contra ese modelo de objetos, consultas que LINQ To SQL traduce a sentencias T-SQL que se envían a la BD. En los siguientes puntos comentaré como realizar operaciones habituales contra una BD: inserción de registros, actualización de datos, borrado de registros y ejecución de sentencias T-SQL (incluidos procedimientos almacenados).


Realizando operaciones contra la BD


Lo primero que vamos a hacer es añadir un registro en la tabla Md_Clientes de la BD. La clave de creación de registros está en crear una instancia de la entidad Clientes (con el operador new()) que mapea la correspondiente tabla de la BD y en el método Add() que nos permite añadir la instancia creada al objeto equivalente del DataContext que a su vez se encargará de generar la correspondiente sentencia SQL de inserción para poder reflejar el registro en la BD. Así, para crear un nuevo cliente en la BD el código necesario es el siguiente:







            Console.WriteLine(«******************Operando con Entidades******************»);


 


            //Nuevo registro


            Console.WriteLine(«Insercción»);


            Clientes nuevoCliente= new Clientes();


            nuevoCliente.ID_Cliente=»72678934C»;


            nuevoCliente.NombreCliente=»Ángel Álvarez»;


            nuevoCliente.CiudadCliente=»Ponferrada»;


            //Añadimos el registro


            BD2.Customers.Add(nuevoCliente);


            //Forzamos que los datos se vuelquen en la BD


            BD2.SubmitChanges();


            //Comprobamos que el registro se ha añadido


            var ClienteAñadido=


                (from c in BD2.Customers


                 where c.ID_Cliente==»72678934C»


                 select c).First();


             Console.WriteLine(«El cliente con NIF {0}, Nombre {1} ha sido añadido»,


                            ClienteAñadido.ID_Cliente,ClienteAñadido.NombreCliente);


             Console.ReadLine();


 Como vemos, añadir un nuevo registro es realmente sencillo. Basta con crear una nueva instancia de la entidad Clientes, completar sus propiedades y añadir la instancia en el DataContext para que los datos se vuelquen en memoria. Además, para que los cambios sean efectivos tenemos que llamar al método SubmitChanges() que se encarga de iniciar una transacción (por defecto las transacciones en LINQ To SQL son de tipo implicitico para las operaciones de inserción y borrado de datos, la actualización de datos en cambio es automática y no se hace en el contexto de una transacción). Después de llevar los cambios a la BD, comprobamos que estos son efectivos definiendo una consulta en la que filtremos por ID_Cliente y a la que le aplicamos el método First() que retorna un objeto en lugar de una colección de objetos. La salida por pantalla que se obtiene es la siguiente:



Actualizar o borrar un registro es igualmente sencillo en la BD con LINQ es también bastante sencillo. Para actualizar un registro basta con recuperar el registro o registros a actualizar, cambiar sus propiedades y registrar los cambios en la BD con SubmitChanges(). Y para borrar el registro, utilizamos el método Remove() para borrar el registro o registros recuperados desde la BD y que nos interesa eliminar. El código que permite actualizar y borrar el cliente que hemos añadido antes es el siguiente:







            //Actualización del registro           


            Console.WriteLine(«Actualización»);


            ClienteAñadido.NombreCliente=»Ángel Fernández»;


            ClienteAñadido.CiudadCliente=»La Bañeza»;


 


            Console.WriteLine(«El cliente con NIF {0}, ha sido actualizado con los siguientes datos:n  


            Nombre {1} y Ciuad {2}»,  ClienteAñadido.ID_Cliente,


           ClienteAñadido.NombreCliente, ClienteAñadido.CiudadCliente);


            Console.ReadLine();


 


            //Borrado de registro


            Console.WriteLine(«Borrado»);


            BD2.Customers.Remove(ClienteAñadido);


            BD2.SubmitChanges();


 


            foreach(var cliente in BD2.Customers)


            {


                Console.WriteLine(«NIF: {0}, Nombre: {1},Coche: {2} «,


                    cliente.ID_Cliente,cliente.NombreCliente,cliente.CiudadCliente);


            }


            Console.ReadLine();


 La salida por pantalla correspondiente es la siguiente:



Utilizando comandos T-SQL en LINQ To SQL


Para acabar con este post, os voy a mostrar como LINQ To SQL permite definir y utilizar comandos SQL o procedimientos almacenados (SP’s) en el modelo de entidades definido. Como os podréis imaginar, el uso de estos comandos y/o procedimientos almacenados pasa por definir los correspondientes métodos en la clase que definir nuestro DataContext y decorarlos de manera adecuada.  Lo primero que vamos a hacer es definir estos métodos en la clase comentada. Como veréis en el código, he añadido dos:


·         El método Actualizar que nos permitirá actualizar un registro de la BD. Como veis, la clave de este método está en decorarlo con el atributo Update y luego definir en su cuerpo el uso del método ExecuteCommand (propio de la clase DataContext). Como se ve en el código, este método recibe como argumentos la sentencia T-SQL y los parámetros necesarios para ejecutarla de manera adecuada.


·         El método ExtraerClientesVehiculosByCiudad, que devuelve el resultado de ejecutar un cierto SP. La clave de este método está en decorarlo con el atributo StoreProcedure, en definir adecuadamente el tipo de dato a devolver, y en pasarle adecuadamente los parámetros que se necesiten para ejecutar el SP. Como veis, utilizar SP’s en nuestro modelo de entidades es más complicado que utilizar simples sentencias T-SQL, veamos el código necesario para definir los dos métodos.







            //Método que ejecuta un comando T-SQL


            [UpdateMethod]


            public void Actualizar(Clientes Cliente)


            {


                Console.WriteLine(«Actualización de datos»);


               


                ExecuteCommand(«UPDATE [Md_Clientes] set [CiudadCliente]={0} WHERE ID_Cliente={1}»,


                                Cliente.CiudadCliente,Cliente.ID_Cliente);


            }


             //Método que ejecuta un SP complejo


             [StoredProcedure (Name=»ExtraerClientesVehiculosByCiudad»)]


            public StoredProcedureResult<ClientesVehiculos>


                 ExtraerClientesVehiculosByCiudad(


                  [Parameter(Name=»CiudadCliente»,DBType=»NVarChar(50)»)] string CiudadCliente)


             {


                 return this.ExecuteStoredProcedure<ClientesVehiculos>(


                     ((MethodInfo)(MethodInfo.GetCurrentMethod())),CiudadCliente);


             }


Como vemos en el código anterior, utilizar un SP es más complejo, por lo que merece la pena explicar un poco más en detalle cómo se define el método:


·         Decorar el método con el atributo StoredProcedure, además especificaremos el parámetro Name en el que indicamos el nombre del SP a ejecutar.







CREATE PROCEDURE ExtraerClientesVehiculosByCiudad


 @CiudadCliente NVARCHAR(50)


AS


                        select C.ID_Cliente, C.CiudadCliente, V.MarcaVehiculo


                                   from Md_Clientes C


                                   left join Md_Vehiculos V on


                                   C.ID_Cliente=V.ID_Cliente


                                     where CiudadCliente=@CiudadCliente


                                     order by C.CiudadCliente  


GO



 ·         En la declaración del método, vemos que es de un tipo especial: StoredProcedureResult<T>, que hereda de las clases IEnumerable<T> y StoredProcedureResult.  Por lo tanto, implementa una interfaz de tipo IEnumerable y necesitaremos definir la correspondiente clase que nos de la descripción de la colección. Lógicamente, la clase que implementemos se tendrá que corresponder con lo que esperamos que nos devuelva el SP.







public class ClientesVehiculos


        {


            private string _ID_Cliente;


           


            public string ID_Cliente


            {


                get {return this._ID_Cliente;}


                set {this._ID_Cliente=value;}


            }


 


            private string _CiudadCliente;


           


            public string CiudadCliente


            {


                get {return this._CiudadCliente;}


                set {this._CiudadCliente=value;}


            }


 


            private string _MarcaVehiculo;


           


            public string MarcaVehiculo


            {


                get {return this._MarcaVehiculo;}


                set {this._MarcaVehiculo=value;}


            }


        }


Cómo parámetros del método, definiremos justamente los parámetros que espera el SP y decorándolos adecuadamente para que sean del tipo que esperado. En nuestro caso, el SP necesita un único parámetro, @CiudadCliente, por lo que en código lo especificamos mediante el parámetro CiudadCliente convenientemente decorado con el atriburo parameter 


( [Parameter(Name=»CiudadCliente»,DBType=»NVarChar(50)»)] string CiudadCliente)


 


·         Lo siguiente que hacemos es ejecutar el procedimiento y devolver el resultado de la ejecución. Para ello llamamos al método ExecuteStoredProcedure de la clase DataContext, el cuál recibe como parámetros un tipo MethodInfo y el parámetro o parámetros que necesita el SP para ser ejecutado correctamente.


Nota: Por supuesto, se pueden definir métodos que ejecuten un procedimiento almacenado y devuelvan tipos simples (como un entero) o tipos más complejos en los que a priori no sepamos el formato (en cuanto a número de columnas) que nos devuelva el SP (en este caso se utiliza un tipo shape).


Una vez que hemos definido los métodos necesarios para habilitar el uso de sentencias T-SQL y SP’s en nuestro código, utilizarlos es sencillo. El código necesario para utilizar los métodos anteriores es el siguiente:







            Console.WriteLine(«****************Uso de Comandos T-SQL****************»);


            Console.WriteLine(«Sentencia T-SQL»);


              var ClienteExistente=


                (from c in BD2.Customers


                 where c.ID_Cliente==»71505286B»


                 select c).First();


 


            Console.WriteLine(«Datos antes de actualizar ID={0},Ciudad={1}»,


                ClienteExistente.ID_Cliente,ClienteExistente.CiudadCliente);          


            ClienteExistente.CiudadCliente=»Oviedo»;


            BD2.Actualizar(ClienteExistente);


            Console.WriteLine(«Datos después de actualizar ID={0},Ciudad={1}»,


                ClienteExistente.ID_Cliente,ClienteExistente.CiudadCliente);


            Console.WriteLine(«n»);


           


            Console.WriteLine(«****************Procedimiento almacenado****************»);


            StoredProcedureResult<ClientesVehiculos> resultados =


                BD2.ExtraerClientesVehiculosByCiudad(«Madrid»);


            foreach(ClientesVehiculos resultado in resultados)


            {


                Console.WriteLine(«ID Cliente: {0},Ciudad Cliente: {1},Marca Vehiculo:  


                                           {2}»,resultado.ID_Cliente,resultado.CiudadCliente,resultado.MarcaVehiculo);


            }


            Console.ReadLine();


 La salida por pantalla para este caso es la siguiente:



Y hasta aquí (que no es poco), lo que os quería contar sobre LINQ To SQL. Espero vuestros comentarios y que os hayan resultado interesantes los aspectos cubiertos en el post. Os dejo para que os podáis descargar y probar el código del proyecto con todas las funcionalidades de LINQ To SQL y el script de creación de la BD (tablas, datos y procedimientos almacenados incluidos) que he usado para las pruebas. Os animo a que probéis LINQ , a mi me ha impresionado bastante y resulta muy natural utilizarlo si estás familiarizado con T-SQL. En otro post os contaré de que va LINQ To XML.

Comenzando con LINQ: Bases y Consulta de Datos en Memoria!

Como os comentaba en un post previo, uno de los cometidos como integrantes de un centro de innovación de Microsoft es estar al día de las últimas novedades en las que Microsoft está trabajando, conocer en detalle dichas novedades, cacharrear con ellas, etc, siempre teniendo en mente que resulta realmente complicado seguir el ritmo de creación de Microsoft. Por este motivo, nos planteamos mes a mes que tecnologías sería interesante conocer para transmitir el conocimiento a las empresas de Cantabria y ponerlas en la parrilla de salida hacía las futuras tendencias tecnológicas. Pues bien, una de las últimas tecnologías que hemos estado probando es LINQ, y francamente estoy sorprendido tanto por sus características (construida sobre las innovaciones de lenguaje de C# y VB 9.0) como por sus prestaciones: habilitar el uso de consultas a objetos de memoria, orígenes de datos, XML o cualquier otro “ente” que se nos ocurra (a partir de la capacidad de extensibilidad de LINQ).


Para los nuevos en la materia, LINQ o Language Integrated Query es un framework de consultas que habilita el uso de órdenes tipo SQL integradas en el lenguaje de programación a partir de una serie de operadores estándar de consulta que permiten realizar consultas sobre cualquier colección de tipo IEnumerable<T>. La arquitectura de LINQ es la siguiente:



¿Qué proporciona LINQ?


Antes de entrar en materia, no está de más tener claro que ventajas o características nos da LINQ. Destacaría las siguientes:


·         Un framework unificado de acceso a objetos, datos y XML. Y que además es completamente extensible.


·         Consultas tipo SQL en código C# y VB:


o   Sintaxis potente, similar a T-SQL.


o   La curva de aprendizaje es reducida.


·         Comprobación de tipos e intellisense.


·         Modelo de proveedores extensible, ya se han definido extensiones hacia SQL (LINQ To SQL) y XML (LINQ to XML).


·         Una serie de operadores estándar de consulta out-of-the-box, que permiten definir operaciones de recorrido, filtro y proyección de modo declarativo en el lenguaje.


·         ¿Se os ocurren más?


Bueno, después de esta pequeña introducción a LINQ (para más información, os recomiendo que vayáis al sitio oficial del proyecto LINQ), es hora de pasar a la acción. También es recomendable la información y ejemplos que en el sitio de El Guille han colgado tanto Guillermo Som como Octavio Hernández  En este post comentaré algunas de las innovaciones del lenguaje (para C#) conocidas, así como el modo de definir consultas integradas en el lenguaje contra un objeto de memoria.


C# 3.0: Innovaciones en el lenguaje


Como comentábamos, LINQ se basa en muchas de las innovaciones del lenguaje que viene con C# 3.0 y VB 9.0. Antes de empezar con las innovaciones, seguro que alguno os está preguntando que se necesita para poder probarlas:


·         Evidentemente, Visual Studio 2005 (No necesitáis bajaros la última CTP de Orcas).


·         LINQ Preview para VB y C# de Mayo de 2006 (no he encontrado una versión posterior para poder utilizarla con VS 2005, pero para el propósito de este post es suficiente), que os podéis descargar en este enlace. Al instalar esta Preview, veréis que además de las correspondientes plantillas de proyecto, se instalarán un montón de ejemplos y HandOn labs tanto para VB como C#.


Una vez instalada la preview, si os vais a VS 2005 veréis que aparece una nueva entrada en los tipos de proyectos: LINQ Preview con las correspondientes plantillas añadidas.



Tras pulsar OK, nos saldrá un mensaje advirtiéndonos que este tipo de proyecto se refiere a una versión no soportada de C# 3.0 y que ciertas funcionalidades podrían no funcionar de manera correcta. Corremos el riesgo, y ya estamos listos para empezar a ver las innovaciones en el lenguaje, las cuales están disponibles (no tenéis más que examinar las referencias que se añaden al proyecto)  a través de los siguientes espacios de nombres:


·         using System.Query;


·         using System.Xml.XLinq;


·         using System.Data.DLinq;


·         using System.Expressions; (está la tenemos que añadir para poder crear árboles de expresión como luego veremos).


Entre estas innovaciones son destacables las siguientes


·         Declaración implícita de tipos, lo que implica que cuando declaramos un objeto no es necesario que especifiquemos su tipo, sino que el compilador es capaz de inferirlo de manera dinámica en tiempo de compilación a partir del valor estático asignado. La clave de la declaración de tipos implícitos está en el uso de la palabra reservada var. En la práctica, esta innovación facilita poder referenciar en nuestro código instancias de tipos anónimos. Veamos un ejemplo de declaración implícita de tipos.







            //Declaración ímplicita de tipos


            var numero=2;


            var cadena=»Hola mundo desde LINQ!»;


 


            Console.WriteLine(«numero es de tipo » + numero.GetType()


                                + «.El valor de numero es » + numero);


            Console.WriteLine(«cadena es de tipo » + cadena.GetType()


                                + «.El valor de cadena es » + cadena);


            Console.ReadLine();


 La salida por pantalla que obtendríamos para este caso es:



La ventaja que nos aporta esta innovación es que libera al desarrollador de tener que definir de manera explícita los tipos que necesite  al programar una aplicación. Esta característica es clave para LINQ, puesto que el desarrollador no necesita definir los tipos que va a devolver una consulta, sino que es el compilador quien dinámicamente va a realizar esta tarea.


·         Tipos anónimos, esta característica permite crear objetos de tipo anónimo, es decir, de nuevo podemos omitir el tipo cuando instanciamos un objeto y este es construido de manera dinámica en tiempo de compilación. Además de poder definir un objeto de manera anónima, es posible definir propiedades asociadas al mismo. Un ejemplo de esta innovación es el siguiente:







            var Santander=new {NombreCiudad=»Santander», PoblacionCiudad=180000};


            Console.WriteLine(«La ciudad de » + Santander.NombreCiudad + » tiene una población de » +


                                Santander.PoblacionCiudad + » habitantes»);


            Console.ReadLine();


            Console.WriteLine(«Santander es de tipo » + Santander.GetType());


            Console.ReadLine();


 En este caso, la salida por pantalla es la siguiente:



Como vemos, aunque hemos declarado un tipo anónimo, el compilador es capaz de inferir el tipo correspondiente y sacar por pantalla los valores de sus propiedades. También vemos que el tipo devuelto es <Projection>f __0 que es el que se infiere en tiempo de compilación.  Si miramos el ensamblado a través de la utilidad reflector, tendremos más información respecto al tipo generado en tiempo de compilación.



 


·         Inicializadores de objetos, esta innovación ya la vimos en la anterior. Lo que permite es inicializar objetos (anónimos o no) en el momento en que los instanciamos.







public class Ciudad


        {


            public string NombreCiudad;


            public int PoblacionCiudad;


        }


        static void Main(string[] args)


        {


 


            Var Santander=new Ciudad{NombreCiudad=»Santander»,


                                        PoblacionCiudad=180000};


            Console.WriteLine(«La ciudad de » + Santander.NombreCiudad +


                                               » tiene una población de » +


                                                Santander.PoblacionCiudad + » habitantes»);


            Console.ReadLine();


            Console.WriteLine(«Santander es de tipo » + Santander.GetType());


            Console.ReadLine();


        }




En este caso, en el código estamos combinando la declaración implícita de tipos y la inicialización de objetos. Por lo que ahora, el tipo devuelto es Ciudad.


·         Métodos de extensión, o dicho de otra forma: mecanismos que habilitan la extensión de objetos. La base de esta innovación consiste en crear clases de extensión que nos permitan extender la funcionalidad de tipos existentes a partir de crear nuevos métodos. Los métodos de extensión son métodos estáticos que son  habilitados como métodos de extensión a través de la palabra reservada this.


 






 static class ExtensionDeTipos


        {


            public static string Saludar(this string nombre)


            {


                return («Hola » + nombre + «!»);


            }


 


 


        }


        static void Main(string[] args)


        {


           


            string nombre = «Mundo»;


            Console.WriteLine(nombre.Saludar());


            Console.ReadLine();


 


        }


 



Mediante el código anterior, hemos definido una clase estática dentro de la cuál definiremos nuestros métodos de extensión. En este caso, estamos extendiendo nombre que es de tipo cadena mediante el método Saludar y como vemos la clave de esta extensibilidad es la palabra reservada this.



Una característica interesante de los métodos de extensión es que se pueden añadir a cualquier tipo, incluyendo tipos genéricos como List <T> y Dictionary <T>.


 


·         Expresiones lambda, que habilitan el uso de condiciones sin tener que especificar el tipo. En la práctica, las expresiones lambda permiten definir de un modo más claro y directo los métodos anónimos de C # 2.0 (quizás esta es una de las innovaciones más destacadas de C# 3.0 y una de las bases de LINQ junto con los métodos de extensión). Para entender esta innovación, no está de más recordar que es un método anónimo en C# 2.0:







List <string> NombresLongitudMayor5=nombres.FindAll(delegate(string s)


                                                                       {return(s.Length)>=5;});


       


Pues bien, la expresión anterior se puede escribir de manera más directa en C# 3.0 utilizando una expresión lambda, que unida a otras innovaciones simplifica mucho el código anterior







var NombresLongitudMayor5=nombres.FindAll(s => s.Length>=5);


 


Como vemos, una expression lamba consta de tres elementos:


o   Un parámetro de lista (s), que puede ser tipado de manera explícita o implícita. En el ejemplo, de nuevo estamos utilizando tipado implícito.


o   El token =>.


o   La expresión a aplicar (la longitud sea 10).


Un ejemplo completo de uso de expresiones lambda sería el siguiente:







var nombres=new List<string>();


            nombres.Add(«Luis»);


            nombres.Add(«Juan Carlos»);


            nombres.Add(«Pepe»);


            nombres.Add(«Ramón»);


 


            var NombresLongitudMayor5=nombres.FindAll(s => s.Length>=5);


            foreach(string nombre in NombresLongitudMayor5)


            {


                Console.WriteLine(nombre);


            }


            Console.ReadLine();


 


·         Árboles de expresión, que son representaciones más eficientes en memoria de una expresión lambda. La idea de los árboles de expresión es dar una visión más transparente e implícita que el código IL, además de habilitar la utilización de expresiones lambda como datos en tiempo de ejecución. La clave de la definición de árboles de expresión está en un nuevo tipo: Expression <T>. Veamos un ejemplo:







Expression<Func<string,bool>> NombresLongitudMayor5= s => s.Length>=5;


 Console.WriteLine(NombresLongitudMayor5);


 Console.ReadLine();


         Código que produce la siguiente salida:




Como vemos, el árbol de expresión nos permite traducir la expresión lambda a datos que son manipulables. Pero, ¿Para qué nos sirven los árboles de expresión? Pues, y como se comenta en la documentación disponible del proyecto LINQ, para una implementación de acceso a base de datos, nos permitiría traducir los árboles a sentencias apropiadas para un tipo particular de BD. De hecho, esto es lo que se hace en LINQ to SQL (anteriormente DLinq), en el que los árboles de expresión son traducidos a sentencias T-SQL que puedan ser evaluadas en la BD.


LINQ to Objetcs: Consultas a objetos de memoria


Después de haber introducido algunas de las novedades del lenguaje que vendrán con C# 3.0 y de modo equivalente con VB y sobre las que se apoya LINQ, ya es hora de ver las posibilidades de LINQ en la práctica. Básicamente, LINQ utiliza estas innovaciones para definir habilitar el uso de consultas integradas en el lenguaje de programación. Para ello define una serie de operadores estándares de consulta que son métodos de extensión definidos sobre IEnumerable <T>, y cuyas características más relevantes son las siguientes:


·         Están construidos a partir de las innovaciones en el lenguaje vistas.


·         Permiten trabajar con datos en memoria. Esto es especialmente interesante para cuando trabajemos contra datos de una BD, de un archivo XML o cualquier otro origen de datos.


·         Las operaciones siempre se realizan en memoria.


Aquí os dejo el listado de los operadores estándar de consulta. Para conocer en detalle dichos operadores, os recomiendo la lectura de la traducción por Octavio Hernández del documento The LINQ Proyect. Este documento junto con otros también traducidos al castellano lo podéis encontrar aquí.












































Tipo de Operador


Operador


Restricción


Where


Proyección


Select, SelectMany


Ordenación


OrderBy, ThenBy


Agrupamiento


GroupBy, Reverse, GroupJoin


Cuantificadores


Any, All


Particionado


Take, Skip, TakeWhile, SkipWhile


Conjuntos


Distinct, Union, Intersect, Except


Elementos


First, FirstOrDefault, ElementAt


Agregación


Aggregate,Count, Sum, Min, Max, Average


Conversión


ToArray, ToList, ToDictionary


Casting


OfType<T>


Join


Join, GroupJoin, On


 


Además del uso de los operadores estándar de consultas, LINQ utiliza otra innovación conocida como la evaluación diferida de consultas, que implica que una consulta no es realmente evaluada hasta el momento en que se itera con ella. La ventaja de esta técnica es que las consultas van a poder ser evaluadas múltiples veces.


Sin más, vamos a ver un ejemplo de LINQ contra objetos. El código sería el siguiente: 






        //Clase a partir de la cuál definiremos el objeto en memoria a consultar


        public class Ciudad


        {


            public string NombreCiudad;


            public int PoblacionCiudad;


 


        }


        static void Main(string[] args)


        {


         //Objeto en memoria sobre el que vamos a habiltiar la consulta


         var ciudades=new List<Ciudad>{


                        {NombreCiudad=»Santander»,PoblacionCiudad=180000},


                        {NombreCiudad=»León»,PoblacionCiudad=150000},


                        {NombreCiudad=»Ponferrada»,PoblacionCiudad=75000},


                        {NombreCiudad=»Madrid»,PoblacionCiudad=4000000}


         };


        //Consulta…


        var consulta=


                from c in ciudades


                where c.PoblacionCiudad>150000


                select new {c.NombreCiudad,c.PoblacionCiudad};


 


       Console.WriteLine(«Ciudades con más de 150000 habitantes: «);


      //Aquí es dónde realmente se evalua la consulta…


        foreach(var c in consulta)


        {


            Console.WriteLine(«Ciudad: {0}, Población {1}»,


                            c.NombreCiudad,c.PoblacionCiudad);


        }


 


        Console.ReadLine();


 


En el código anterior hemos utilizado varias de las innovaciones vistas, a ver quién me las dice a partir de leer el post (así podré medir si me explico bien o no :P). La salida por pantalla sería la siguiente:



Bueno, pues hasta aquí el primer post que desde el CIIN hacemos de LINQ. Espero vuestros comentarios y respuestas a la dudilla planteada.

Comenzando con WF: Paso de Datos y Runtime Services!

A menudo, en el CIIN se nos pregunta cuál es nuestro cometido, que tipo de cosas hacemos, si conocemos tal tecnología, etc. Bueno, pues la verdad es que hacemos de todo un poco, y uno de nuestros cometidos principales es actuar como difusores de tecnologías .NET en Cantabria, y para ello cada semana damos un pequeño seminario al que asisten distintas empresas TIC de Cantabria. Hasta ahora hemos dado seminarios de WSSv3 / MOSS, WF, WCF y estamos preparando otros (LINQ, BizTalk 2006, etc.). En mi caso, ya he dado un par de seminarios introductorios de WF, por lo que como ha hecho Carlos Segura con sus excelentes posts sobre WF, me he animado a contar algunas de las cosillas que contamos en el seminario que impartimos en el CIIN. En concreto, comentaré aspectos referentes al paso de datos a un workflow y el cómo añadir servicios al runtime de ejecución de workflows. Antes de empezar, no está de más recordar cómo es la arquitectura  de WF:



Como comentaba Carlos Segura en su primer post, un workflow está compuesto por una serie de componentes, llamados actividades, que se enlazan de manera adecuada (y en función de estilo de creación del workflow, secuencial o máquina de estados) para constituir un modelo ejecutable de un cierto proceso de negocio. Como vemos en el diagrama de la arquitectura de WF, tenemos 5 elementos fundamentales:


·         Un diseñador visual de workflows, para crear nuestros worflows en base a arrastrar actividades sobre una superficie por diseño. Por defecto el entorno natural para la creación de workflows es Visual Studio 2005 que actúa como hoster del diseñador visual de WF. Para tener disponible este diseñador, necesitamos las extensiones de WF para VS 2005. Como ya nos comentó Unai, el diseñador de WF se puede “sacar” del entorno de WF y hostearlo en otros entornos. Dos ejemplos de esto los tenemos con NetfxLive (un diseñador de workflows embebido en una aplicación web) o la aplicación WFPad.


·          Una librería de actividades base, que son un conjunto de actividades que por defecto nos da WF para crear nuestros workflows. Algunos ejemplos de actividades son: IfElse, While, Policy, Delay, etc. Además de estas, ya tenemos actividades especificas para crear workflows en el entorno de WSSv3: SendEmail, OnTaskChange, OnWorkflowActivated, etc (estas actividades las tenéis a partir de la dll Microsoft.SharePoint.WorkflowActions.dll que tenéis que hay que añadir como referencia en nuestro proyecto de wrokflow para sharepoint y en en la toolbox de VS 2005), interactuar con servicios de WCF, y otros muchos ejemplos en el sitio oficial de WF .


·         El runtime engine de WF, que es el responsable de la ejecución, control y gestión de las distintas instancias de workflows que tengamos creadas. Los componentes fundamentales son el motor de ejecución, los core services  para el motor de ejecución (que se encargan de controlar la ejecución de los workflows), y el motor de reglas que permite utilizar reglas de negocio en nuestros workflows.


·         Los servicios de runtime, que son piezas enchufables que permiten extender las capacidades del motor de ejecución de WF habilitando la posibilidad de persistir el estado de ejecución y los datos de un workflow, realizar un seguimiento (tracking) de las instancias del workflow en ejecución, comunicarlo con el exterior (aplicaciones locales y remotas), etc.


·         Proceso de host, que se encarga de hospedar tanto el runtime engine como los runtime services, es decir, es el que en la práctica se encarga de instanciar y arrancar el motor de ejecución de WF, así como de instanciar los servicios de runtime necesarios.  En este link podéis ver un estupendo resumen de todo lo que se puede hacer en el proceso de host de WF. Finalmente comentaros que la naturaleza del proceso de Host de WF es realmente variada, desde un servicio Windows o una aplicación de consola, pasando por un formulario Windows (justo el caso que veremos en este post) o una página ASP.NET, hasta entornos más complicados como WSSv3 o la BTS 2006 R2.


Después de este repaso arquitectónico, es momento de comenzar con los dos tópicos de este post: paso de datos a un workflow y uso de los servicios de runtime.


Paso de datos a un workflow


En el segundo post de su serie sobre WF, Carlos Segura ya nos introdujo uno de los mecanismos para pasar datos a un workflow: el paso de parámetros. La idea es explicaros un poco más en detalle cómo se implementa este paso de parámetros y que otras posibilidades tenemos para pasar información a nuestro workflow. Para ello, voy a partir de un sencillo workflow que calcula el factorial de un número y muestra un mensaje con el resultado.



Para pasar datos al workflow mediante el método de paso por parámetros, lo primero que tenemos que hacer es definir las propiedades necesarias en nuestro workflow. En nuestro caso vamos a pasar al workflow un único parámetro que vamos a pasar es el número cuyo factorial queremos calcular. Por lo tanto, en la vista de código de nuestro workflow sólo tendremos que añadir una propiedad que luego utilizaremos en la lógica que modela el comportamiento del workflow (a través de las actividades condicionales y las actividades code):







        private string numero;


 


        public string Numero


        {


            get { return numero; }


            set { numero = value; }


        }


 


El siguiente paso es definir el paso de parámetros en el momento que se crea la instancia del workflow. Esto lo haremos en el hosting process que hayamos definido. En mi caso, he creado un simple formulario de Windows en el que en una caja de texto se recoge el valor numérico cuyo factorial queremos calcular.  El código necesario para instanciar el runtime de WF, crear una instancia  de nuestro workflow y pasar el parámetro sería el siguiente:







public partial class Form1 : Form


    {


        private WorkflowRuntime WR;


        public Form1()


        {


            InitializeComponent();


        }


        private void button1_Click(object sender, EventArgs e)


        {


            if (WR==null)


            {


                WR = new WorkflowRuntime();


                WR.StartRuntime();


            }


            Dictionary<string, object> parametros = new Dictionary<string, object>();


            Parámetros.Add(“Numero”,textBox1.Text);


            WorkflowInstance MiInstancia=WR.CreateWorkflow(typeof(Ejemplo_WF.Workflow1),parametros);


            MiInstancia.Start();


        }


       


        private void Form1_FormClosed(object sender,FormClosedEventArgs e)


        {


            if (WR!=null)


            {


                if (WR.IsStarted)


                {


                    WR.StopRuntime();


                }


               }


        }


    }


 


Como vemos en el código anterior, los pasos para hostear el runtime engine de WF, crear una instancia de nuestro workflow y realizar el paso de parámetros son:


·         Crear una instancia del runtime engine, y a continuación iniciarlo.


·         Definir un diccionario de parámetros al que posteriormente le añadimos nuestro parámetro. Este diccionario lo hemos que definir de acuerdo a dos restricciones: (i) Los parámetros sólo pueden ser de tipo string y (ii) El nombre de los parámetros ha de coincidir con las propiedades que hayamos definido en nuestro workflow.


·         Crear una instancia de nuestro workflow y realizar el paso de parámetros.


Comentaros que he incluido una función asociada a la acción de cerrar el formulario y en la que se para el runtime de WF en el caso de que decidamos cerrar el formulario. Por supuesto, la gestión del runtime de WF se puede hacer mucho mejor, aquí tenéis un ejemplo de un servicio Windows que actúa como hoster de WF y que incluye métodos para gestionar el inicio, parada o ejecución del workflow a partir de sobreescribir los métodos correspondientes. Finalmente, si probamos nuestro workflow con paso de parámetros, este sería el resultado:



¿Qué otras formas hay para pasar datos a un workflow? Además del paso de parámetros, WF define cuatro mecanismos adicionales:








 


ü Mediante el uso de eventos a través de las actividades HandleExternalEvent y CallExternalMethod que habilitan respectivamente el paso de datos desde el exterior al workflow y desde el workflow al exterior.


ü Mediante la invocación de servicios web, a través de las actividades InvokeWebService, WebServiceInput y WebServiceOutput, que habilitan el paso de información en modo remoto bidireccional, entrante y saliente.


ü La misma idea que con la invocación de servicios web, pero invocando servicios WCF y utilizando para ellos las actividades ya disponibles en Codeplex.


 



 


ü  Crear una actividad customizada que nos permita realizar el paso de datos.


ü  Definir el paso de datos en una actividad de tipo code.


Añadiendo servicios al runtime


Añadir servicios al runtime de ejecución resulta bastante sencillo. En esta parte del post voy a explicar cómo se añade el servicio de tracking y que utilidad nos aporta en cuanto a información proporcionada.  Cómo su nombre indica, este servicio permite monitorizar y realizar un seguimiento de los workflows en ejecución. Como se dice en el SDK de WF, este servicio permite que el proceso de host “observe” las instancias de workflows en tiempo de ejecución a partir de capturar distintos eventos que se lanzan durante dicha ejecución. Hasta aquí todo está claro, pero ¿qué hace el servicio de tracking con la información observada? La respuesta es depende del servicio de tracking que utilicemos. WF trae por defecto una implementación out-of-the-box para este servicio que escribe la información de tracking en una base de datos creada a priori. Por supuesto, podemos definir nuestros propios servicios de tracking customizados y almacenar la información de tracking en otros contenedores (otros gestores de BD, archivos XML, etc.).


En el ejemplo de este post, voy a utilizar el servicio de SQLTracking que por defecto trae WF. Para ello, y como paso previo, necesito establecer la infraestructura necesaria para el servicio, es decir, crear la BD SQL Server y los elementos necesarios para habilitar su uso. Como os podéis imaginar, .NET Framework 3.0 viene con los scripts SQL necesarios (ubicados en C:WINDOWSMicrosoft.NETFrameworkv3.0Windows Workflow FoundationSQLEN):


·         Tracking_Schema.sql, que crea la estructura de tablas necesaria para almacenar la información de tracking en la BD que especifiquemos.


·         Logic_Schema.sql, que crea un conjunto de procedimientos almacenados necesarios para que el servicio de SQLTracking pueda almacenar la información de tracking, así como otros útiles para consultar dicha información.



Una vez creada la estructura necesaria para el servicio SQLTracking, para poder utilizarlo basta con añadir a nuestro servicio de host (en este caso un formulario Windows) las siguientes líneas:







SqlTrackingService ServicioTracking = new SqlTrackingService


                    («Data Source=MOSS2007;Initial Catalog=WF_Tracking;Integrated Security=True»);


WR.AddService(ServicioTracking);


 Nota: Para poder utilizar el servicio de SQLTracking, tenemos que añadirlo al runtime de WF antes de iniciarlo.


Sin más, si ejecutamos nuestra aplicación y realizamos la siguiente consulta SQL en la BD de tracking creada, obtendremos información interesante relativa a las etapas de ejecución por las que ha pasado una o varias instancias de un workflow.







SELECT  TrackingWorkflowEvent.Description as Fase_Ejecución,


WorkflowInstanceEvent.EventDateTime as Inicio_Fase,


WorkflowInstanceEvent.WorkflowInstanceInternalId as WorkflowId,


WorkflowInstance.WorkflowInstanceId as InstanciaWorkflow,


Type.TypeFullName as NombreWorkflow


FROM   WorkflowInstanceEvent


INNER JOIN TrackingWorkflowEvent ON


WorkflowInstanceEvent.TrackingWorkflowEventId = TrackingWorkflowEvent.TrackingWorkflowEventId AND


WorkflowInstanceEvent.TrackingWorkflowEventId = TrackingWorkflowEvent.TrackingWorkflowEventId


INNER JOIN WorkflowInstance ON


WorkflowInstance.WorkflowInstanceInternalId=WorkflowInstanceEvent.WorkflowInstanceInternalId


INNER JOIN Type ON


Type.TypeId = WorkflowInstance.WorkflowTypeId


order by WorkflowInstanceEvent.WorkflowInstanceInternalId



De acuerdo al SDK de WF, se puede hacer el tracking a nivel de instancia de workflow de los eventos definidos en la enumeración TrackingWorkflowEvent.



Además de los eventos a nivel de instancia, el servicio SQLTracking permite capturar y almacenar información relativa al estado de ejecución de las actividades que componen un workflow. Los estados posibles de ejecución de una actividad está recogidos dentro de la enumeración ActivityExecutionStatus y son 6: Initialized, Executing, Canceling, Closed, Compensating y Faulting. Así, si ejecutamos la consulta siguiente contra la BD de tracking, obtendremos información interesante relativa a los estados de ejecución de las actividades que componen nuestro workflow.


 







select T.TypeFullName as Instancia_Workflow,AI.QualifiedName as Actividad,


A.ParentQualifiedName as ActividadPadre,AES.Description as Estado_Ejecucion


from ActivityInstance AI


INNER JOIN ActivityExecutionStatusEvent AESE ON


AESE.ActivityInstanceId=AI.ActivityInstanceId


INNER JOIN ActivityExecutionStatus AES ON


AES.ExecutionStatusId=AESE.ExecutionStatusId


INNER JOIN WorkflowInstance WI ON


WI.WorkflowInstanceInternalId=AESE.WorkflowInstanceInternalId


INNER JOIN Type T ON


T.TypeId = WI.WorkflowTypeId


INNER JOIN Activity A ON


A.WorkflowTypeId=WI.WorkflowTypeId AND


A.QualifiedName=AI.QualifiedName


order by AESE.ActivityExecutionStatusEventId



Bueno, pues esto ha sido todo. Me podría extender con la parte de los servicios de runtime de WF, pero creo que el post ya es lo suficientemente largo y espeso. Espero que os hayan resultado interesantes los temas tratados.