Business Intelligence Blog

Welcome on Business Intelligence Blog !!!

This blog is dedicated to Business Intelligence technologies under SAS9 and SQL Server 2005.

Calendrier

Décembre 2009
L M M J V S D
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
<< < > >>

MVP Blog

Recommander

W3C

  • Flux RSS des articles
Lundi 18 mai 2009 1 18 05 2009 23:12

Dans la série des addin interessant, on a un petit nouveau
http://www.codeplex.com/OlapPivotTableExtend
Je crois que je vais m'en servir pour mon Lab du club BI.

(merci de l'info à M. Thiphaine / MVP Lead & L. Billon).

Par Renaud Harduin
Ecrire un commentaire - Voir les commentaires - Recommander
Mardi 5 mai 2009 2 05 05 2009 19:03

Nous avons donc rajouté notre source vers la table FactInternetSales, ainsi qu'un multicast après celle-ci. La boite multicast a pour objet de dupliquer les flux.

Dans un premier temps nous allons lui attacher un tri afin de faire une jointure avec les produits liés.

Et passer de :


Au dataflow suivant :


Ajout de la source de données Produit.

La première étape est logiquement le rajout de la source au produit. Quelques commentaires :

Tout comme la source OLE des faits, nous rajoutons dynamiquement un OLEDB source. Nous le lions au même ConnectionManager que la source de faits attendu qu'il s'agit de la même base de donnée (c'est 'utilité du CM).

La requête de cette source portera sur les produits :

Select * from DimProduct

Je ne reviens pas dans le détail car nous avons dé jà vu comment ajouter une source dans un dataflow.

 

private void addPdtSource()

{

    _pdtDatasource = _mainpipe.ComponentMetaDataCollection.New();

    //_oledbDatasource.ComponentClassID = "DTSAdapter.OleDbSource";

    _pdtDatasource.ComponentClassID = "DTSAdapter.OLEDBSource";

   

   

    // Instanciate an OleDB source Instance for design time based on OleDB Source

    _pdtSourceInstance = _pdtDatasource.Instantiate();

   

   

    // ProvideComponentProperties acts as a kind of constructor and build input, output ...

    _pdtSourceInstance.ProvideComponentProperties();

   

    // Relink runtime oledb component with the package connection manager defined before

    _pdtDatasource.RuntimeConnectionCollection[0].ConnectionManagerID = _sourceCM.ID;

    _pdtDatasource.RuntimeConnectionCollection[0].ConnectionManager

                    = runtime.DtsConvert.GetExtendedInterface(_sourceCM);

   

    /* Each dataflow component has properties :

     * ms-help://MS.VSCC.v90/MS.VSIPCC.v90/MS.SQLSVR.v10.en/s10is_5techref/html/065a337e-9a0d-4a5e-aa45-87da58fa37c3.htm

     * 1 - Common Properties :

     * ms-help://MS.VSCC.v90/MS.VSIPCC.v90/MS.SQLSVR.v10.en/s10is_5techref/html/51973502-5cc6-4125-9fce-e60fa1b7b796.htm

     * 2 - Custom Properties for oledb source :

     * ms-help://MS.VSCC.v90/MS.VSIPCC.v90/MS.SQLSVR.v10.en/s10is_5techref/html/1361a37f-d647-416b-bb96-542b5258f38d.htm#oledb

     */

   

    _pdtSourceInstance.SetComponentProperty("AccessMode", 2);

    _pdtSourceInstance.SetComponentProperty("SqlCommand", "Select * from DimProduct");

   

    _pdtDatasource.Name = "OLE select Product";

    _pdtDatasource.Description = "OLE select Product";

   

   

    _pdtSourceInstance.AcquireConnections(null);

    _pdtSourceInstance.ReinitializeMetaData();

    _pdtSourceInstance.ReleaseConnections();

   

}

 

Ajout des tris :

Petite nouveauté dans notre dataflow, les SORT Component. Nous en ajoutons 2 instances au sein de la méthode AddSort.

private void addSort()

        {

            // First sort

            _firstSort = _mainpipe.ComponentMetaDataCollection.New();

            _firstSort.ComponentClassID = "DTSTransform.Sort";

            wrapper.CManagedComponentWrapper firstSortInstance = _firstSort.Instantiate();

            firstSortInstance.ProvideComponentProperties();

            _firstSort.Name = "SRT First Sort";

   

Tout l'ajout d'une source OLEDB, l'ajout d'un tri se fait par l'ajout d'un ComponentMetadata (par la collection ad hoc).

Ce tri sera alors relatif à la table de fait via le multicast, une fois lié comme suit :

_mainpipe.PathCollection.New().AttachPathAndPropagateNotifications(

   _multicast1.OutputCollection[0], _firstSort.InputCollection[0]);

 

Le fait de rajouter ce lien crée dynamiquement un OutputCollection sur le multicast.

Il faut enfin spécifier sur quelle colonne nous trions notre flux :

wrapper.IDTSVirtualInput100 vInput =  _firstSort.InputCollection[0].GetVirtualInput();

 

wrapper.IDTSVirtualInputColumn100 vColumn = vInput.VirtualInputColumnCollection["ProductKey"];

 

wrapper.IDTSInputColumn100 col = firstSortInstance.SetUsageType(_firstSort.InputCollection[0].ID, vInput, vColumn.LineageID, wrapper.DTSUsageType.UT_READONLY);

 

   

// As sort component has custom properties, Column has also custom properties for sort

// see ms-help://MS.VSCC.v90/MS.VSIPCC.v90/MS.SQLSVR.v10.en/s10is_5techref/html/56f5df6a-56f6-43df-bca9-08476a3bd931.htm#sort

firstSortInstance.SetInputColumnProperty(_firstSort.InputCollection[0].ID, col.ID, "NewSortKeyPosition", 1);

 

Quelques commentaires sur ce qui précède :

Nous ne pouvons pas accéder directement aux listes de colonnes etc.… Nous le ferons via la Notion de VirtualInput qui donne une vue sur la collection. De même, nous retrouvons la clef « Productkey » par la collection VirtualInputcollection[object].

Nous faisons cela car nous souhaitons positionner un tri sur cette colonne. Tout comme nous avons vu que les composants avaient des propriétés communes, des propriétés spécifiques, telles que des propriétés sur les colonnes. Dans notre cas il s'agit de la propriété « NewSortKetPosition », qui va nous permettre d'indiquer que la colonne pointée par col.ID, sera la première clef de tri.

Pour finir, nous ajoutons dans le corps de cette méthode la création du deuxième sort en relation avec la source créées ci-dessus :

    // 2nd sort

    _secondSort = _mainpipe.ComponentMetaDataCollection.New();

    _secondSort.ComponentClassID = "DTSTransform.Sort";

    wrapper.CManagedComponentWrapper secondSortInstance = _secondSort.Instantiate();

    secondSortInstance.ProvideComponentProperties();

    _secondSort.Name = "SRT Second Sort";

   

    _mainpipe.PathCollection.New().AttachPathAndPropagateNotifications(

       _pdtDatasource.OutputCollection[0], _secondSort.InputCollection[0]);

   

    wrapper.IDTSVirtualInput100 vInput2 = _secondSort.InputCollection[0].GetVirtualInput();

    wrapper.IDTSVirtualInputColumn100 vColumn2 = vInput2.VirtualInputColumnCollection["ProductKey"];

    wrapper.IDTSInputColumn100 col2 = secondSortInstance.SetUsageType(_secondSort.InputCollection[0].ID, vInput2, vColumn2.LineageID, wrapper.DTSUsageType.UT_READONLY);

    // As sort component has custom properties, Column has also custom properties for sort

    // see ms-help://MS.VSCC.v90/MS.VSIPCC.v90/MS.SQLSVR.v10.en/s10is_5techref/html/56f5df6a-56f6-43df-bca9-08476a3bd931.htm#sort

    secondSortInstance.SetInputColumnProperty(_secondSort.InputCollection[0].ID, col2.ID, "NewSortKeyPosition", 1);

   

}

 

Le tour est complet. Reste à lier les deux flux sortant des tris sur un « Merge Join », dans le prochain post….

 

 

 

Par Renaud Harduin - Publié dans : API SSIS
Ecrire un commentaire - Voir les commentaires - Recommander
Mercredi 29 avril 2009 3 29 04 2009 21:54

Dans notre processus, nous souhaitons exporter des faits, ainsi que les données de références qui y sont rattachées.

Cela suppose donc de gérer deux flux :

  • Un qui pilotera l'export des produits
  • Un qui sera le « dump » des faits

Dans cette optique, un multicast nous permettra de rattacher 2 flux à notre source.

Sa définition est assez triviale, et suit la même logique que la définition de la source de données, modulo, la classe à monter bien entendu.

 

private wrapper.IDTSComponentMetaData100 _multicast1;

private wrapper.CManagedComponentWrapper _multicastInstance1;

private wrapper.IDTSPath100  _path1;    

 

 

private void addMulticast()

{

    /* Instanciate a multicast component */

    _multicast1 =  _mainpipe.ComponentMetaDataCollection.New();

    _multicast1.ComponentClassID = "DTSTransform.Multicast";          

   

    _multicastInstance1 = _multicast1.Instantiate();

    _multicastInstance1.ProvideComponentProperties();

    _multicast1.Name = "MLT FactInternetSales";

   

    /* Add paths and link oledb source to multicast component */

    _path1 = _mainpipe.PathCollection.New();

    _path1.AttachPathAndPropagateNotifications(_oledbDatasource.OutputCollection[0], _multicast1.InputCollection[0]);

}

 

Quelques commentaires sur ce que nous faisons dans cette méthode :

Tout d'abord l'objet multicast n'a pas de propriétés particulières.

Ce qui est important c'est que nous tirons aussi un trait entre la source données (cf. précédent article), et notre nouveau composant.

Pour cela, nous créons un IDTSPath (chemin). Nous lions ensuite un composant, et plus particulièrement son output ( _oledbDatasource.OutputCollection[0] ) vers un autre composant en pointant cette fois-ci vers son input (_multicast1.InputCollection[0]), avec la méthode AttachPathAndPropagateNotifications .

Cette méthode comme son nom l'indique attache ces deux composants en termes de IDTSPath, mais surtout propage les métadonnées (les colonnes et leur type) d'un composant vers l'autre, et notifie les changements de métadonnées.

Il faudra donc bien garder à l'attention ces Path et veiller à alimenter correctement pour propager les flux de données.

 

Par Renaud Harduin - Publié dans : API SSIS
Ecrire un commentaire - Voir les commentaires - Recommander
Lundi 27 avril 2009 1 27 04 2009 21:57

Dans le précédent article, j'ai créé mon package et lui ajoutais un data flow (MainPipe).

Nous allons maintenant aborder l'aspect accès aux données dans ce billet. Au sein de l'ETL Microsoft, nous définissons des ConnectionManager dont l'objet est centraliser, de manière générique, les définitions et l'accès aux sources de données (et donc réduire la redondance de paramétrage)

(Tout le code source est disponible sur CodePlex : www.codeplex.com/BILAB, dans l'onglet source, répertoire POC/BILABHistory, solution BIHistory.sln. La classe concernée est MulticastExport.cs dans le sous projet BILAB.Technical.History.PackageBuilder)

Ajout des Gestionnaires de Connexion

Les gestionnaires de connexion (ConnectionManager) font l'objet d'une collection _p.Connections au sein de package. Vous pouvez en ajouter comme suit :

private runtime.ConnectionManager _sourceCM;

 

private void addSourceCM()

{

    _sourceCM = _p.Connections.Add("OLEDB");

    _sourceCM.Name = "AdventureDWSource";

    _sourceCM.ConnectionString = @"Provider=SQLOLEDB.1;Password=myuser;Persist Security Info=True;User ID=myuser;Initial Catalog=AdventureWorksDW;Data Source=localhost";

}


Les sources peuvent être FLATFILE, ADO, ODBC... fonction du besoin.

Ajout de la source au sein du DataFlow

Définir une connexion ne suffit pas en tant que tel à accéder aux données, il faut ajouter une source au sein de notre DataFlow, plus précisément au MainPipe :

private wrapper.IDTSComponentMetaData100 _oledbDatasource;

private wrapper.CManagedComponentWrapper _sourceInstance;

 

private void addSourceConnection()

   {

       _oledbDatasource = _mainpipe.ComponentMetaDataCollection.New();           

       _oledbDatasource.ComponentClassID = "DTSAdapter.OLEDBSource";

<…/>


Le pipeline SSIS est en fait composé d'objets de type ComponentMetadata qui représente chacune des tâches que vous vous pouvez glisser sur le designer SSIS.
Ce type vous permet d'accéder aux propriétés génériques (Commons) des tâches. Nous le "typons" en utilisant à nouveau les CLSID. 

Vous trouverez ici la référence des API Communes à toutes les tâches :

http://msdn.microsoft.com/en-us/library/ms136001.aspx

Plus précisément : http://msdn.microsoft.com/en-us/library/ms135950.aspx

 

Dans le modèle SSIS, nous devons aussi créer une instance de type « source OLE DB » (et non, ce n'est toujours pas fait...) avec la méthode _oleDbSource.Instanciate qui nous renvoie CManagedComponentWrapper qui comme son nom l'indique est un wrapper vers l'objet (COM encore) sous jacent :

<…/>

 

// Instanciate an OleDB source Instance for design time based on OleDB Source

_sourceInstance = _oledbDatasource.Instantiate();

// ProvideComponentProperties acts as a kind of constructor and build input, output ...//

_sourceInstance.ProvideComponentProperties();

 

<…/>

Si je refais un détour sur le développement de composants SSIS, je vous avais proposé il ya quelques temps une destination XML (qui ne passait pas par du DOM, mais par un XMLWriter) :

http://www.techheadbrothers.com/Articles.aspx/developpement-composant-integration-services-ssis

Et c'est en surchargeant la méthode ProvideComponentProperties que j'ajoutais les propriétés dynamiquement à mon composant.

http://www.techheadbrothers.com/Articles.aspx/developpement-composant-integration-services-ssis-page-3

Il faut donc bien comprendre que la méthode ProvideComponentProperties agit comme un véritable constructeur sur l'instance. Dès lors, nous aurons accès aux propriétés propres au PipelineComponent, et notamment lui la possibilité de lier notre source un connectionManager.

<…/>

// Relink runtime oledb component with the package connection manager defined before

            _oledbDatasource.RuntimeConnectionCollection[0].ConnectionManagerID = _sourceCM.ID;

            _oledbDatasource.RuntimeConnectionCollection[0].ConnectionManager

                            = runtime.DtsConvert.GetExtendedInterface(_sourceCM);

<…/>

Nous pouvons aussi spécifier le mode d'accès aux données ainsi que la requête (propriétés spécifiques):

<…/>

_sourceInstance.SetComponentProperty("AccessMode", 2);

_sourceInstance.SetComponentProperty("SqlCommand", "Select * from FactInternetSales");

   

_oledbDatasource.Name = "OLE select from Fact InternetSales";

_oledbDatasource.Description = "OLE select from Fact InternetSales";

<…/>

En complément au lien MSDN sur les propriétés communes, vous trouverez sur le lien suivant les propriétés propres à la source OLE DB :

http://msdn.microsoft.com/en-us/library/ms135923.aspx

Comme dernière étape à notre processus, nous allons faire en sorte que notre composant se connecte à sa source de données pour la valider et récupérer la liste des colonnes à extraire de la source comme suit:

<…/>

   _sourceInstance.AcquireConnections(null);

    _sourceInstance.ReinitializeMetaData();

    _sourceInstance.ReleaseConnections();

   

}

<…>

 

MulticastExport en action …

Enfin si je replace le tout dans l'appel :

public void Export()

        {

            _p = new runtime.Package();

            _p.Name = "MulticastExport";

            _p.CreatorComputerName = System.Environment.MachineName;

            _p.CreatorName = System.Environment.UserName;

   

            this.addDataflow();

            this.addSourceCM();

            this.addSourceConnection();

            //this.addMulticast();

            //this.addPdtSource();

            //this.addSort();

            //this.addMerge();

            //this.addFactDestination();

   

            runtime.Application a = new Microsoft.SqlServer.Dts.Runtime.Application();

            a.SaveToXml("ZBILAB.History.dtsx", _p, null);

        }

 

Nous retrouvons notre package avec une Connexion :


Et une source dans le dataflow :


Les propriétés « populées » avec nos paramètres :


Le vecteur colonne préparé suite à la validation de la source :

La suite …

A partir de cette source nous allons continuer à construire notre flux et ajouter un multicast qui nous permettra de faire un flux vers un fichier texte, et un flux vers une tâche Merge Join.

 

Par Renaud Harduin - Publié dans : API SSIS
Ecrire un commentaire - Voir les commentaires - Recommander
Jeudi 23 avril 2009 4 23 04 2009 23:44
Commençons par les basiques. La première étape consiste tout simplement à créer un objet package et à lui ajouter :
- Un data flow (objet pipeline)
- Un connection Manager qui contiendra la définition de notre base
et à le sauvegarder.

Je crée donc une classe Export :

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

using runtime = Microsoft.SqlServer.Dts.Runtime;

using runtimeWrapper = Microsoft.SqlServer.Dts.Runtime.Wrapper;

using wrapper = Microsoft.SqlServer.Dts.Pipeline.Wrapper;

 

 

namespace BILAB.Technical.History.PackageBuilder

{

 

    public class MulticastExport

    {

        private runtime.Package _p;

        private runtime.TaskHost _taskhost;

        private wrapper.MainPipe _mainpipe;



A noter les 3 imports:
- Un runtime qui représente le moteur SSIS
- RuntimeWrapper "masque" les objets (COM) sous jacents
- et enfin wrapper est le wrapper des composants du dataflow (pipeline)

La methode Export sera celle appelée par notre client :

public void Export()

{

    _p = new runtime.Package();

    _p.Name = "MulticastExport";

    _p.CreatorComputerName = System.Environment.MachineName;

    _p.CreatorName = System.Environment.UserName;

 

    this.addDataflow();

    this.addSourceCM();

    //this.addSourceConnection();

    //this.addMulticast();

    //this.addPdtSource();

    //this.addSort();

    //this.addMerge();

    //this.addFactDestination();

 

    runtime.Application a = new Microsoft.SqlServer.Dts.Runtime.Application();

    a.SaveToXml("ZBILAB.History.dtsx", _p, null);

}


Comme vous pouvez le voir, cette méthode va créer le package _p, ajouter le dataflow, la connection et enfin sauvegarder le package via un objet Application qui représente le runtime (le troisième paramètre à null sera utilisé pour gérer les évènements)

Ajout du DataFlow :

private void addDataflow()

       {

           /*

            * Common properties of taskhost

            * ms-help://MS.VSCC.v90/MS.VSIPCC.v90/MS.SQLSVR.v10.en/s10is_1devconc/html/1b725922-ec59-4a47-9d55-e079463058f3.htm

            *

            * Detail :

            * ms-help://MS.VSCC.v90/MS.VSIPCC.v90/MS.SQLSVR.v10.en/s10is_7ismrf/html/e39480e5-613f-4bf4-87b1-249942ed9a35.htm

            */

 

            /* Add Method

             * ms-help://MS.VSCC.v90/MS.VSIPCC.v90/MS.SQLSVR.v10.en/s10is_7ismrf/html/a0d4c1cf-d4a3-4297-8892-7b13f235fe9d.htm

             * 

             */

 

           _taskhost = (runtime.TaskHost)_p.Executables.Add("DTS.Pipeline");

           _taskhost.Name = "DFT History";

           _mainpipe = (wrapper.MainPipe)_taskhost.InnerObject;

 

           // Another example of taskhost creation

           //runtime.Executable e = _p.Executables.Add("Microsoft.SqlServer.Dts.Tasks.BulkInsertTask.BulkInsertTask, Microsoft.SqlServer.BulkInsertTask, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91");

           //runtime.TaskHost t = (runtime.TaskHost)e;

           //t.Name = "REH";        

       }



Ajouter un DataFlow consiste à ajouter un objet dit Executable. Les Executable sont respectivements :
- les itérateurs for
- les séquences
- les dataflow / TaskHost

Vous trouverez l'architecture des composants sur :
=> http://msdn.microsoft.com/en-us/library/ms137681.aspx
Ces objets ont des propriétés communes
==> http://msdn.microsoft.com/en-us/library/ms137728.aspx

Dans la pratique, nous ajoutons une tâche en faisant référence à son CLSID (vue base de registre) comme dans notre exemple :


mais cela peut être aussi par les assemblies comme l'exemple (en commentaire) du bulk insert .

Pour finir, nous récupérons la classe hébergée et instanciée dans le taskhost via _taskhost.InnerObject
Par Renaud Harduin - Publié dans : API SSIS
Ecrire un commentaire - Voir les commentaires - Recommander
Créer un blog sur over-blog.com - Contact - C.G.U. - Rémunération en droits d'auteur - Signaler un abus