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).
Welcome on Business Intelligence Blog !!!
This blog is dedicated to Business Intelligence technologies under SAS9 and SQL Server 2005.
| Novembre 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 | ||||||||||
|
||||||||||
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).
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 :
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();
}
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….
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 :
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.
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)
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.
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.
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();
}
<…>
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 :
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.
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;
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);
}
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";
}