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.