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.