DNM+ Online
dotnetmania 2.0
Usando Integration Services programáticamente para integrar bases de datos (y II)
Este artículo es la segunda parte de una serie en la que mostramos la creación de un paquete de integración de SSIS que permite la consolidación de diferentes bases de datos con una misma estructura en una única base de datos, así como la automatización de ese proceso mediante una aplicación .NET.

En nuestra entrega anterior, presentamos el paquete tipo de SSIS que nos serviría para integrar información; este paquete quedaba tal y como se muestra en la figura 1. Algunos os preguntaréis ¿y por qué SSIS? Esto mismo puede hacerse con T-SQL mediante procedimientos almacenados, con la ventaja de que no es necesario aprender a utilizar un entorno nuevo. La respuesta es simple: velocidad y escalabilidad. Preguntaos si podríais importar y consolidar, digamos, 30 millones de filas desde un servidor IBM AS-400 u Oracle a un Microsoft SQL Server consumiendo en torno a unos 15 minutos mediante procedimientos almacenados, teniendo en cuenta que probablemente tendríais que hacer un JOIN una vez traídos los datos para actualizar insertar y eliminar los registros. En un paquete de SSIS bien formado y con los orígenes de datos debidamente eficaces se pueden lograr estas velocidades, aunque como objetivo en una máquina mediana podríamos poner el de un millón de filas consolidadas por minuto. Además, en nuestros amadísimos procedimientos T-SQL probablemente tendríamos que leer la tabla más de una vez (para distinguir entre registros nuevos, modificados y eliminados), exceptuando el caso de que usásemos el nuevo comando MERGE de SQL Server 2008. En resumen: la herramienta adecuada para integrar información es SSIS por más que amemos los lenguajes SQL; esta herramienta se rentabilizará a poco que le dediquemos un poquito de tiempo y aprendamos a manejarla. Nuestro código de ejemplo está desarrollado para la versión de SSIS de SQL Server 2005. El modelo de objetos en 2008 cambia ligeramente, aunque en nada que no sea lo bastante obvio un vez que estudiéis este código. Creando el paquete de forma programática En nuestra aplicación .NET, lo primero que necesitaremos será agregar las referencias a los ensamblados necesarios; estos se muestran en la figura 2. Crear el paquete En este momento, ya estamos en disposición de enfrentarnos a nuestro folio en blanco. Comenzaremos por crear ese lienzo en el que dibujar nuestros componentes y lo que es común a todos nuestros flujos de datos, las conexiones a origen y destino. Para ello, crearemos una función que se encargue del trabajo. El código de esta función se muestra en el listado1. Añadiendo un DataFlow El siguiente paso que tenemos que dar consiste en añadir un DataFlow a nuestro paquete; en realidad, deben ser tantos como necesitemos, uno por cada una de las tablas que intervengan en nuestro proceso de consolidación. Para conseguir encapsular esta funcionalidad, crearemos un método que permite generar el DataFlow de forma parametrizada; su código se presenta en el listado2. La principal particularidad del código del listado 2 radica en la llamada a pk.Executables.Add("SSIS.Pipeline.2"). El lector podría pensar ¿de dónde ha salido este SSIS.PipeLine.2? Realmente, la forma de obtener esa referencia ha sido mirando un paquete ya hecho, una práctica que he venido siguiendo a menudo durante la construcción de esta aplicación. Un paquete de SQL Server Integration Services se define en un fichero XML, y estudiar este fichero va a ser un método bastante socorrido para obtener los nombres y referencias de cada uno de los objetos que estamos usando. Concretamente, el fragmento de XML en el que se hace uso de SSIS.Pipeline.2 es el siguiente:

<DTS:Property DTS:Name="CreationName"> SSIS.Pipeline.2 </DTS:Property> Comencemos ahora a construir nuestro flujo de datos. Si recordamos la figura 1, dentro del flujo de datos necesitaremos un origen OLEDB para conectar a la base de datos de origen. En ese origen, además, tendremos que especificar el comando SQL que se encargará de traer los datos. Para construir ese comando, lo que vamos a hacer es consultar los metadatos, y además intentaremos hacerlo de la forma más estándar posible, en nuestro caso, usando las tablas de metadatos del esquema INFORMATIONSCHEMA (listado 3). El ejemplo está pensado para hacer uso de la base de datos AdventureWorks. Como podrá observar, el comando devuelve la lista de campos que conforman la clave primaria, algo que también nos resultará necesario para construir adecuadamente el objeto de conexión al origen. Como particularidad, note que hemos añadido un alias a todas las columnas. Esto es debido a que las columnas de destino se van a llamar de la misma forma que las de origen, y por lo tanto coincidirían los nombres; la forma que usaremos para distinguir unas de otras será añadiendo el prefijo ORIGEN en las tablas de origen, y DESTINO_ en las de destino. De esta forma, los metadatos de SQL Server Integration Services no tendrán coincidencias en nombres y evitaremos malos entendidos y confusiones en los componentes. También crearemos una función para devolver un DataSet con la información que necesitamos. Esta función se muestra en el listado 4. La función anterior devuelve un DataSet que contiene dos objetos DataTable, el primero de ellos conteniendo el resultado de ejecutar una instrucción SELECT con una cláusula ORDER BY basada en la clave primaria (no hemos preparado el ejemplo para que funcione con tablas sin una clave primaria; esta tarea se la dejamos al lector). Para la tabla Person.Address, el comando devuelto será:

SELECT [AddressID] as [ORIGENAddressID], [AddressLine1] as [ORIGENAddressLine1], [AddressLine2] as [ORIGENAddressLine2], [City] as [ORIGENCity], [StateProvinceID] as [ORIGENStateProvinceID], [PostalCode] as [ORIGENPostalCode], [rowguid] as [ORIGENrowguid], [ModifiedDate] as [ORIGENModifiedDate]
FROM [Person].[Address]
ORDER BY AddressID

Por su parte, el segundo DataTable contendrá una única fila con el campo AddressID, que es la clave primaria de la tabla. Una vez que tenemos el comando SELECT y una colección de las columnas que forman la clave primaria de la tabla, podemos construir una función que reciba este DataSet, un paquete y un flujo de datos, y que añada un componente de origen de datos, asignando a la misma vez las propiedades de ordenación adecuadas para que el componente sea consciente de que los datos que está devolviendo están ordenados. Esta función se presenta en el listado 5. Al principio del listado 5 podemos ver cómo se crea el componente, que en forma de variable hemos llamado ConexionAOrigen y que será un componente de tipo DTSAdapter.OLEDBSource.2. Una vez más, usted podría preguntarse de dónde ha salido este nombre; la respuesta es rebuscada pero sencilla: si consultamos el XML de un paquete ya hecho, y dependiendo de la versión de SQL Server que estemos usando, el componente puede tener un nombre u otro, así como también un CLSID u otro. Este CLSID es lo que realmente nos aparece en el XML (listado 6). Si buscamos ese CLSID en el Registro de Windows utilizando RegEdit (figura 3), encontraremos el nombre del componente. Insisto, seguramente esto también esté documentado en MSDN, pero me pareció interesante explicar esta forma de obtener dicha información. El código para conectar un componente al destino de la información es muy similar al que acabamos de ver, con la única diferencia que la conexión y base de datos son diferentes. Dependiendo también del problema de negocio que queramos solucionar, podrían haber nuevos matices. Por ejemplo, lo que hemos visto corresponde al caso en que estamos consolidando en un modelo 1:1, es decir, un solo origen a un solo destino. Si estuviésemos consolidando en un modelo N:1, es decir, varios orígenes a un único destino consolidado, entonces tendríamos que añadir lógica para segmentar la información, añadiendo un campo idOrigen, creando variables en los paquetes de SSIS para distinguir de qué origen estamos trayendo los datos y asociando esas variables como parámetros a los comandos SELECT para que la información de origen se compare con su equivalente en destino y no con toda la base de datos consolidada.

Lo que nos queda En la siguiente y última entrega, veremos cómo añadir los componentes Join y Conditional Split y los comandos INSERT, UPDATE y DELETE necesarios para completar el círculo; también veremos cómo quedará nuestro flujo de control y haremos alguna reflexión adicional sobre posibles escenarios de uso para esta forma de hacer las cosas.

blog comments powered by Disqus
autor
referencias