Quantcast
Channel: Microsoft SQL Server Integration Services
Viewing all 149 articles
Browse latest View live

SQL Saturday #221

$
0
0









The powerpoint (in Dutch!) of my SQL Saturday presentation about SSIS Performance Tuning is available for download. Added some screenshots for most demo's and a couple of URL's for additional information.

:-)

















SQL Rally 2013
And some really good news... SQL Saturday in autumn 2013 will be replaced by SQL Rally. Three days instead of one day! SQL Rally will be hosted in Amsterdam on November 6-8 2013.

Performance Best Practice: Network Packet Size

$
0
0
Network
If your SSIS package gets its data from a SQL Server database located on an other machine, then the data of your query will go from the SQL Server machine over the network to your SSIS machine. This is done in small network packets with a default size of 4096 bytes (4 kilobytes).

Buffers
The Data Flow Task uses buffers to transport multiple rows of data through the data flow. Its default size is 10485760 bytes (10 megabytes). This means you need 2560 network packets (10485760 / 4096) to fill one buffer. By increasing the size of the packets you need less packets to fill that buffer. With the maximum size of 32768 bytes (32 kilobytes) you only need 320 network packets (10485760 / 32768) to fill one buffer.
This could, depending on the quality of your network, improve the performance significant (For poor performing networks you need to resend a lot of packets, making large packets inefficient).
Default Max Buffer Size: 10 MB



















You can adjust the default size of 4096 bytes within SQL Server. However I strongly recommend not to change that in SQL Server. You should add the network packet size to the connectionstring to override this default value.
Do not change within SQL Server




















OLE DB:
Data Source=mySqlServer\SQL2012;Initial Catalog=myDatabase;Provider=SQLNCLI11.1;Integrated Security=SSPI;Auto Translate=False;Packet Size=8192;

ADO.NET
Data Source=mySqlServer\sql2012;Initial Catalog=myDatabase;Integrated Security=True;Packet Size=10000;Application Name=SSIS-package;



SSIS OLE DB Connection Manager
Within SSIS you can change this value in the OLE DB Connection Manager or directly in the Connectionstring property. The maximum value for the network packet size is 32768 bytes, but the property is zero-based, so the max number is 32767. The value 0 means use the SQL Server default.

Network Packet Size property OLE DB






















SSIS ADO.Net Connection Manager
The ADO.Net Connection Manager has a default of 8000 bytes. this one isn't zero-based, so its max value is 32768.
Network Packet Size property ADO.Net


















Note 1: if you are using package configurations on the connectionstring property then don't forget to add this value to the connectionstring in your config table/file.
Note 2: you could enable jumbo frames on your network to increase performance even more, but you should consult a network specialist for that.

More info: Blog of Henk van der Valk, SQLCAT and MSDN.

Performance Best Practice: more rows per buffer

$
0
0
SSIS uses buffers to transport a set of rows through the data flow task. In general the less buffers you need to transport all rows from the source to the destination the faster it is.

You can compare a buffer going through a data flow, with a fire bucket going through a medieval line of people trying to extinguish a fire.

Buffers going through a data flow


You can extinguish the fire faster by adding an extra line of people (parallel process) or my making the buckets larger (but not too large, otherwise they can't lift the buckets).


For this example I use a table with Google Analytics data from my blog staged in a database table.
Table with quite large columns



















Changing Buffer Size
By default the buffer size is 10 Megabytes (1024 * 1024 * 10 = 10,485,760 bytes). You can adjust this size for each Data Flow Task (see Data Flow Task Property DefaultBufferSize).
DefaultBufferSize 64KB <> 100MB






















I have a Package that pauses between buffers with a Script Component. This allows me to see how many records there are in one buffer. In my case 1 row of Google Analytics data is about 29000 bytes and if I run my Data Flow Task I can see that 360 rows of data fit in to my 10MB buffer.
360 rows = ±10MB



















By making my buffer twice the size you will see that the number of rows in my buffer doubles. You can change the buffer size up to 100MB, but you have to remember that there are multiple buffers going through your data flow at the same time. If you don't have enough memory to fit all those buffers, then SSIS will swap memory to disk making it very slow! So only use bigger buckets if you can lift them...
721 rows = ± 20MB



















Adjust row size
Never use a table as a datasource! Selecting a table is akin to "SELECT *..." which is universally recognized as bad practice(*). Always use a query. This allows you to skip unwanted columns and rows.

Unchecking "Available External Columns" at the Columns pane of the OLE DB Source Editor will prevent unwanted column in the buffer, but the data will have to go from SQL Server to SSIS. Removing unwanted column with a query will reduce the number of network packets going from a SQL Server machine to your SSIS machine.

Use query instead of whole table




















Let's remove an unwanted column to see the effect. Removing the nvarchar(4000) column [ga:referralPath] will reduce the total recordsize from 29030 to 21028 bytes. Now 497 rows instead of 320 will fit in my 10MB buffer.
Removing unwanted columns => more rows in buffer



















Adjust Column Size
Often column are just unnecessary too big in the source. If you can't change the source then you could change it in the source query with a CAST or CONVERT. For example the nvarchar(4000) column for page titles is way to big. My titles are not that long and the characters should fit in varchar. Let's CAST it to varchar(255) and see how many extra rows will fit in the buffer.
Resizing datatypes => more rows in buffer



















Now we have 787 rows instead of 320 rows in the 10mb buffer. That should speed up this package!

Note: if you change datatypes in an existing source query, then you have to uncheck and check the changed columns in the Columns pane of the OLE DB Source editor. This will refresh the metadata in the data flow. Otherwise you will get an error like: Validation error. DFT - Visits: DFT - Visits: Column "ga:pageTitle" cannot convert between unicode and non-unicode string data types.
Uncheck and check changed columns























Add WHERE clause
Don't use a Conditional Split Transformation to remove records after an database table source. Instead use a WHERE clause in your source query. The Conditional Split is a so called Magic Transformation. It will only visually hide records instead of removing them from the buffer. And the records are unnecessary transported from SQL Server to SSIS.
Add WHERE clause instead of Conditional Split

















RunInOptimizedMode
This is a Data Flow Task property that, if set to true, will 'remove' unused columns and unused transformations to improve performance. Default value is true, but this property isn't the holy grail and you shouldn't rely on it while developing your packages.
RunInOptimizedMode






















Enable logging on Data Flow Task
There are some events that you could log to get information about the buffer:
User:BufferSizeTuning: This will tell you how many rows there were in the buffer.
  • Rows in buffer type 0 would cause a buffer size greater than the configured maximum. There will be only 787 rows in buffers of this type.
  • Rows in buffer type 0 would cause a buffer size greater than the configured maximum. There will be only 1538 rows in buffers of this type.
User:PipelineInitialization: This will tell you something about which settings are used.
  • The default buffer size is 10485760 bytes.
  • Buffers will have 10000 rows by default.
  • The data flow will not remove unused components because its RunInOptimizedMode property is set to false.
BufferSizeTuning and PipelineInitialization



















Performance Counters
SSIS comes with a set of performance counters that you can use to monitor the performance of the Data Flow Task. More about that in a later blog. For now see msdn.

Summary
Optimize Buffer Size
Use Source Query
Adjust Row Size
Adjust Column Size
Use Where clause


More info: Blog of Matt Masson, *Jamie ThomsonSQLCAT and MSDN

Performance Best Practice: Flat File (Fast) Parse

$
0
0
Here are some tips to speed up the reading of flat files within the SSIS data flow. They are especially handy for importing large flat files (or when you merge join your small flat file to a large dataset). Start, where possible, by reading the files from a fast drive (a Solid State Disk / preferably not used by Windows or SQL Server) instead of some share.

Minimum datatype
By default all columns are string 50 in a Flat File Connection Manager. To get as many rows in a data flow buffer it's important to use a minimum data type for each column. And if it's for example an integer column then parse it to an integer in the Flat File Connection Manager instead of parsing it in the data flow with a Data Conversion or Derived Column Transformation. Otherwise you end up with two columns in your buffer instead of one.

A good start is to use the Suggest Types button in the Flat File Connection Manager editor. It will scan a couple of rows from your flat file and come up with a minimum data type.

Suggest Types



















Unused columns
In the Flat File Connection Manager it's impossible to skip columns that you don't need, but to minimize pressure on performance you should not parse unneeded columns. Parsing/converting is expensive. Just leave it string. In the Flat File Source editor you can uncheck the unneeded columns.
Leave it string, don't parse.




















Uncheck unneeded flat file columns



















Fast Parse
If you have a so called 'trusted' source (for example a database export) then you don't have to worry for mistakes in the data types. To speed up the performance you can enable fast parse for all non-string columns. But for date columns you have to be sure that the format is correct. Try using the ISO format YYYY-MM-DD.
You can enable fast parse in the Advanced Editor of the Flat File Source. Right click it and choose "Show Advanced Editor...".
Show Advanced Editor...






















Then go to the Input and Output Properties tab and then to the Output Columns. Select a non-string column and set the FastParse property to true. Repeat this for all non-string columns.
Enable FastParse





















Bulk Insert Task
If your destination is a SQL Server table and you don't need data transformations then you might want to consider/test the Bulk Insert Task as a alternative for the Data Flow Task.

Summary
Minimum data types
Parse in Connection Manager
No unnecessary parsing
Fast Parse

More info: Blog of Jamie Thomson and Henk van der Valk or MSDN

Create your own Foreach Loop Enumerator

$
0
0
Case
I want to create my own custom SSIS enumerator with a GUI. How do you do that?

Solution
For this example I will create a very basic enumerator which you can extend for your own needs.
The enumerator is for SSIS 2008 and 2012 and I will use Visual Studio 2010 to create the enumerator. Programming language is C#. Use this page to translate the code to VB.Net if you prefer that language.
Very very basic enumerator example, but it works























1) Create Visual Studio project
For my enumerator I used two C# Class Library projects. One for the GUI/editor and one for the code. For SSIS 2008 I will use .Net framework 3.5 and for SSIS 2012 I will use .Net framework 4.0
Two project for my enumerator solution




















2) Create key for strongname
You need to strongname your DLL's so that SSIS can use them. More about that in this Codeguru article: Giving a .NET Assembly a Strong Name. Open the Visual Studio 2010 Command Prompt (in Windows start menu). Browse to your project folder and execute the following command to create a key file: sn.exe -k myEnumerator.snk
Microsoft (R) .NET Framework Strong Name Utility


















3) Add key to project
The key file should be added to both projects.

Add key to projects




















And after adding them, you need to sign the projects. Go to the properties of the projects and then to the Signing page. There you can sign the assembly with your newly generated key. Do this for both projects.
Sign Assembly



















4) Adding SSIS reference
We need to add references to SSIS libraries. The GUI project needs two references:
  • Microsoft.SqlServer.Dts.Design
  • Microsoft.SQLServer.ManagedDTS
And the code project only needs one reference:
  • Microsoft.SQLServer.ManagedDTS

For SSIS 2008 they can be found in the program files folder. Something like:
C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SQLServer.ManagedDTS.dll
And for SSIS 2012 they are located in the GAC. Something like:
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.SqlServer.ManagedDTS\v4.0_11.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.ManagedDTS.dll
Add references



















5) Build Events
To use the enumerator dll's in SSIS you need to copy them to the GAC and to the enumerator folder of SSIS. With the Build Events you can do that automatically when you build the visual studio project. Go to the properties of your projects and then to the Build Events. Add the following command to the Post-Build events.

2008
cd $(ProjectDir)
@SET ENUMDIR="C:\Program Files (x86)\Microsoft SQL Server\100\DTS\ForEachEnumerators\"
@SET GACUTIL="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\gacutil.exe"

Echo Installing dll in GAC
Echo $(OutDir)
Echo $(TargetFileName)
%GACUTIL% -if "$(OutDir)$(TargetFileName)"

Echo Copying files to Tasks
copy "$(OutDir)$(TargetFileName)" %ENUMDIR%


2012
cd $(ProjectDir)
@SET ENUMDIR="C:\Program Files (x86)\Microsoft SQL Server\110\DTS\ForEachEnumerators\"
@SET GACUTIL="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe"

Echo Installing dll in GAC
Echo $(OutDir)
Echo $(TargetFileName)
%GACUTIL% -if "$(OutDir)$(TargetFileName)"

Echo Copying files to Tasks
copy "$(OutDir)$(TargetFileName)" %ENUMDIR%

Post-Build Events




















6) Gui project code
To keep everything clear and easy to explain I created a simplified file enumerator. The GUI has only two textboxes and a button. See the code comments for the explanation.
Two properties: folder and wildcard filter





















// C# code
// The code-behind from our GUI.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.SqlServer.Dts.Runtime; // Added

namespace SSISJoost
{
public partial class myEnumeratorEditor : ForEachEnumeratorUI
{
// Variables needed to communicate with SSIS.
// You could for example get a list of all
// variables or connection managers.
private ForEachEnumeratorHost _feeHost;
private Connections _connections; // not used in this solution
private Variables _variables; // not used in this solution

// Constructor
public myEnumeratorEditor()
{
InitializeComponent();
}

// This method is executed when you open the editor.
public override void Initialize(ForEachEnumeratorHost FEEHost, IServiceProvider serviceProvider, Connections connections, Variables variables)
{
// Filling those variables so that we read values of two properties
base.Initialize(FEEHost, serviceProvider, connections, variables);
this._feeHost = FEEHost;
this._connections = connections; // not used in this solution
this._variables = variables; // not used in this solution

// Get the properties from the Enumerator. I have added these properties in the
// other project. They are used to store the folder and filter in the package.
if (this._feeHost != null)
{
// Get the Directory value and fill the textbox.
if (this._feeHost.Properties["Directory"] != null)
{
this.txtFolder.Text = (string)this._feeHost.Properties["Directory"].GetValue(_feeHost);
}
// Get the Filter value and fill the textbox.
if (this._feeHost.Properties["Filter"] != null)
{
this.txtFilter.Text = (string)this._feeHost.Properties["Filter"].GetValue(_feeHost);
}
}
}

// This method is execute when you press OK in the editor
public override void SaveSettings()
{
base.SaveSettings();
// When you close(/save) the enumerator then you need to write the values from the
// form to the properties of the enumerator so that they are saved in the package.
this._feeHost.Properties["Directory"].SetValue(this._feeHost, this.txtFolder.Text);
this._feeHost.Properties["Filter"].SetValue(this._feeHost, this.txtFilter.Text);
}

// Open browse folder dialog when you click the button
private void btnBrowse_Click(object sender, EventArgs e)
{
if (DialogResult.OK == folderBrowserDialog.ShowDialog())
{
txtFolder.Text = folderBrowserDialog.SelectedPath;
}
}
}
}


7) Get PublicKeyToken
For the other project you need the PublicKeyToken of the GUI assembly. So first build the GUI project and then, via the same command prompt of step 2, execute the following command in the BIN folder of your GUI project: sn.exe -T SSISJoost.myEnumeratorUI.dll
Copy the number generated. You need it in the next project.














8) The code for the actual work
This is the code from the project that does the actual enumeration. See the comments for the explanation.
// C# code
// The code from the project that does the actual work.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Dts.Runtime; // Added
using Microsoft.SqlServer.Dts.Runtime.Enumerators; // Added
using System.IO; // Added

namespace SSISJoost
{
// Connection to the editor assembly. Copy the PublicKeyToken from the previous step.
[DtsForEachEnumerator(
DisplayName = "myEnumerator",
Description = "A very basic enumerator",
UITypeName = "SSISJoost.myEnumeratorEditor, SSISJoost.myEnumeratorUI, Version=1.0.0.0, Culture=Neutral,PublicKeyToken=629e0ff8812f1e93")]
public class myEnumerator : ForEachEnumerator
{
// The properties of my enumerator that will
// be saved in the xml of the SSIS package.

// A property for the folder
private string _directory;
public string Directory
{
get { return this._directory; }
set { this._directory = value; }
}

// A property for the wildcard filter
private string _filter = "*.*";
public string Filter
{
get { return this._filter; }
set { this._filter = value; }
}

// This validation method is execute when you run the package, but also when
// you click OK in the editor. You can fire warnings and errors.
public override DTSExecResult Validate(Connections connections, VariableDispenser variableDispenser, IDTSInfoEvents infoEvents, IDTSLogging log)
{
// Directory is mandatory
if (string.IsNullOrEmpty(_directory))
{
// Fire error and fail package
infoEvents.FireError(0, "myEnumerator", "Directory is mandatory", "", 0);
return DTSExecResult.Failure;
}

// Filter isn't mandatory, but fire warning that a default will be used
if (string.IsNullOrEmpty(_filter))
{
// Fire warming, but continue
infoEvents.FireWarning(0, "myEnumerator", "Filter is empty. The default *.* will be used.", "", 0);
}
return DTSExecResult.Success;
}

// This is the method that fills the enumerator.
// You can enumerate through just about anything
// you like. This is a simplified file enumerator.
public override object GetEnumerator(Connections connections, VariableDispenser variableDispenser, IDTSInfoEvents events, IDTSLogging log)
{
// Determine the options for getting the files
string startFolder = this._directory;
string filter = "*.*";
System.IO.SearchOption searchOption = SearchOption.TopDirectoryOnly;

// If the filter is "", then just use the "*.*" as default
if (!string.IsNullOrEmpty(this._filter))
{
filter = this._filter;
}

// Create a list that will be returned by this method
List<String> filePaths = new List<String>(); ;

try
{
// Take a snapshot of the file system.
System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);

// Get all the files that match the critery
IQueryable<System.IO.FileInfo> fileList = dir.GetFiles(filter, searchOption).AsQueryable<System.IO.FileInfo>();

// Loop through list and only get the 'Fully Qualified Name' => the complete path
foreach (FileInfo myFile in fileList)
{
filePaths.Add(myFile.FullName);
}
}
catch (Exception ex)
{
// Fire error if something unexpected fails
events.FireError(0, "myEnumerator", "Unexpected Exception: " + ex.Message, "", 0);
}

// return the list
return filePaths.GetEnumerator();
}
}
}

9) The Result
After building / deploying the solution, you need to close/reopen BIDS because the GAC is cached on startup. Now you can use your new enumerator.
New enumerator in action


























10) Download
You can download the complete Visual Studio 2010 example solutions:
SSIS 2008 version
SSIS 2012 version


Note: this is a very basic example. Keep in mind that some subjects like PerformeUpgrade are not explained in this blog.
More info: MSDNDougbert, Graham

Create your own custom task

$
0
0
Case
I want to create my own custom SSIS task with a GUI. How do you do that?


Solution
For this example I will create a very basic task which you can extend for your own needs. It checks whether a file exists. The task is for SSIS 2008 and 2012 and I will use Visual Studio 2010 to create the it. Programming language is C#. Use this page to translate the code to VB.Net if you prefer that language.

My first SSIS task




















1) Create Visual Studio project
For my task I used two C# Class Library projects. One for the GUI/editor and one for the code. For SSIS 2008 I will use .Net framework 3.5 and for SSIS 2012 I will use .Net framework 4.0
Two projects for my Task


















2) Create key for strongname
You need to strongname your DLL's so that SSIS can use them. More about that in this Codeguru article: Giving a .NET Assembly a Strong Name. Open the Visual Studio 2010 Command Prompt (in Windows start menu). Browse to your project folder and execute the following command to create a key file: sn.exe -k myTask.snk

Microsoft (R) .NET Framework Strong Name Utility
















3) Add key to project
The key file should be added to both projects.
Add key to projects


















And after adding them, you need to sign the projects. Go to the properties of the projects and then to the Signing page. There you can sign the assembly with your newly generated key. Do this for both projects.
Sign Assembly


















4) Adding SSIS reference
We need to add references to SSIS libraries. The GUI project needs two references:
  • Microsoft.SqlServer.Dts.Design
  • Microsoft.SQLServer.ManagedDTS
And the code project only needs one reference:
  • Microsoft.SQLServer.ManagedDTS

For SSIS 2008 they can be found in the program files folder. Something like:
C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SQLServer.ManagedDTS.dll
And for SSIS 2012 they are located in the GAC. Something like:
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.SqlServer.ManagedDTS\v4.0_11.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.ManagedDTS.dll
Add references


















5) Build Events
To use the task dll's in SSIS you need to copy them to the GAC and to the task folder of SSIS. With the Build Events you can do that automatically when you build the visual studio project. Go to the properties of your projects and then to the Build Events. Add the following command to the Post-Build events.

2008
cd $(ProjectDir)
@SET TASKDIR="C:\Program Files (x86)\Microsoft SQL Server\100\DTS\Tasks\"
@SET GACUTIL="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\gacutil.exe"

Echo Installing dll in GAC
Echo $(OutDir)
Echo $(TargetFileName)
%GACUTIL% -if "$(OutDir)$(TargetFileName)"

Echo Copying files to Tasks
copy "$(OutDir)$(TargetFileName)" %TASKDIR%


2012
cd $(ProjectDir)
@SET TASKDIR="C:\Program Files (x86)\Microsoft SQL Server\110\DTS\Tasks\"
@SET GACUTIL="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe"

Echo Installing dll in GAC
Echo $(OutDir)
Echo $(TargetFileName)
%GACUTIL% -if "$(OutDir)$(TargetFileName)"

Echo Copying files to Tasks
copy "$(OutDir)$(TargetFileName)" %TASKDIR%

Post-build events
















6) Icons
I have added the same icon file to both projects. In the properties of the icon file you have to set Build Action to "Embedded Resource". In the UI project you can use the icon in your Windows Form. This will show when you edit the task.
Icon for editor




















In the other project you can use this file to give your task a custom icon instead of the default. This will show in the SSIS Toolbox and in the Control Flow. Code is explained later on.
Icon for SSIS Toolbox and Control Flow




















7) Gui project code
To keep everything clear and easy to explain I created a simple task. In the GUI you can choose a file connection manager or a string variable. The task will use the value in runtime to check if the filepath exists. See the code comments for the explanation.

myTaskInterface.cs
// C# code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Runtime.Design;
using System.Windows.Forms;

// Interface for task editor
namespace SSISJoost
{
public class myTaskInterface : IDtsTaskUI
{
private TaskHost _taskHost;
private IServiceProvider _serviceProvider;

public myTaskInterface()
{
}

public void Initialize(TaskHost taskHost, IServiceProvider serviceProvider)
{
this._taskHost = taskHost;
this._serviceProvider = serviceProvider;
}

public ContainerControl GetView()
{
myTaskUI.myTaskEditor editor = new myTaskUI.myTaskEditor();
editor.TaskHost = this._taskHost;
editor.ServiceProvider = this._serviceProvider;
return editor;
}

public void Delete(IWin32Window parentWindow)
{
}

public void New(IWin32Window parentWindow)
{
}
}
}

myTaskEditor.cs (code behind from windows form)
// C# code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing; // Reference added
using System.Linq;
using System.Text;
using System.Windows.Forms; // Reference added
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Runtime.Design;

namespace SSISJoost.myTaskUI
{
public partial class myTaskEditor : Form
{
#region General Task Methods
// Setting and getting taskhost
private TaskHost _taskHost;
public TaskHost TaskHost
{
get { return _taskHost; }
set { _taskHost = value; }
}

// Getting connections, setting is done with ServiceProvider
private Connections _connections;
public Connections connections
{
get { return _connections; }
}

// Gets or sets the serviceprovider. Used for getting
// f.a. the VariablesProvider and ConnectionsProvider
private IServiceProvider _serviceProvider = null;
public IServiceProvider ServiceProvider
{
get
{
return _serviceProvider;
}
set
{
_serviceProvider = value;
_connections = ((IDtsConnectionService)(value.GetService(typeof(IDtsConnectionService)))).GetConnections();
}
}

// Default constructor
public myTaskEditor()
{
InitializeComponent();
}

// Constructor to set taskhost and serviceprovider
// See GetView in myTaskInterface.cs
public myTaskEditor(TaskHost taskHost, IServiceProvider serviceprovider)
{
InitializeComponent();

this.TaskHost = taskHost;
this.ServiceProvider = serviceprovider;
}
#endregion

#region Page Load
private void myTaskEditor_Load(object sender, EventArgs e)
{
// Fill the ComboBox with connection managers from the package,
// but only select FILE, FLATFILE and EXCEL connection managers.
if (this._taskHost != null && this._connections != null)
{
cmbConnectionsSource.Items.Clear();
cmbConnectionsSource.DisplayMember = "Name";
cmbConnectionsSource.ValueMember = "ID";
cmbConnectionsSource.Items.Add("<Choose connectionmanager>");
cmbConnectionsSource.Items.Add("<New connectionmanager>"); // open new connection manager popup
foreach (ConnectionManager connection in this._connections)
{
if (connection.CreationName == "FILE")
{
cmbConnectionsSource.Items.Add(connection);
}
else if (connection.CreationName == "EXCEL")
{
cmbConnectionsSource.Items.Add(connection);
}
else if (connection.CreationName == "FLATFILE")
{
cmbConnectionsSource.Items.Add(connection);
}
}
}

// Fill the ComboBox with variables from the package,
// but only show string variables and non-system variables
if (this._taskHost != null && this._taskHost.Variables != null)
{
cmbVariablesSource.Items.Clear();
cmbVariablesSource.DisplayMember = "Name";
cmbVariablesSource.ValueMember = "QualifiedName";
cmbVariablesSource.Items.Add("<Choose variable>");
cmbVariablesSource.Items.Add("<New variable>"); // open new variabele popup
foreach (Variable variable in _taskHost.Variables)
{
if ((!variable.SystemVariable) && (variable.DataType == TypeCode.String))
{
this.cmbVariablesSource.Items.Add(variable);
}
}
}


// Get the properties from the Task and fill the form.
if (this._taskHost != null)
{
// Only fill form if a value is found in the properties
if (this._taskHost.Properties["HasConnectionmanagerSource"] != null)
{
// A connection manager is selected
if ((bool)this._taskHost.Properties["HasConnectionmanagerSource"].GetValue(_taskHost))
{
this.radConnectionSource.Checked = true;
this.cmbConnectionsSource.Enabled = true;
this.cmbVariablesSource.Enabled = false;
this.cmbVariablesSource.SelectedIndex = 0;
// Set the ConnectionManagerID
if (this._taskHost.Properties["SelectedConnectionManagerIDSource"] != null)
{
object obj = this._taskHost.Properties["SelectedConnectionManagerIDSource"].GetValue(_taskHost);
if (obj == null)
{
// No connection manager in property
this.cmbConnectionsSource.SelectedIndex = 0;
}
else
{
ConnectionManager cm = null;
cm = FindConnectionManager(obj.ToString().Trim());
if (cm != null)
{
// Connection manager found, now select it in combobox
this.cmbConnectionsSource.SelectedItem = cm;
}
else
{
// Connection manager not found
this.cmbConnectionsSource.SelectedIndex = 0;
}
}
}
}
// A variable is selected
else
{
this.radVariableSource.Checked = true;
this.cmbVariablesSource.Enabled = true;
this.cmbConnectionsSource.Enabled = false;
this.cmbConnectionsSource.SelectedIndex = 0;
// Set the VariableID
if (this._taskHost.Properties["SelectedVariableIDSource"] != null)
{
object obj = this._taskHost.Properties["SelectedVariableIDSource"].GetValue(_taskHost);
if (obj == null)
{
// No variable in property
this.cmbVariablesSource.SelectedIndex = 0;
return;
}
Variable var = FindVariable(this._taskHost.Properties["SelectedVariableIDSource"].GetValue(_taskHost).ToString().Trim());
if (var != null)
{
// Variable found, now select it in combobox
this.cmbVariablesSource.SelectedItem = var;
}
else
{
// Variable not found
this.cmbVariablesSource.SelectedIndex = 0;
}
}
}
}
}
// Initial values (for new tasks)
else
{
this.radConnectionSource.Checked = true;
this.cmbConnectionsSource.Enabled = true;
this.cmbConnectionsSource.SelectedIndex = 0;
this.cmbVariablesSource.Enabled = false;
this.cmbVariablesSource.SelectedIndex = 0;
}
}
#endregion

#region Button Methods OK end Cancel
private void btnCancel_Click(object sender, EventArgs e)
{
// Cancel editor / close window
this.DialogResult = DialogResult.Cancel;
this.Close();
}

private void btnSave_Click(object sender, EventArgs e)
{
// Safe Source values (radiobutton, connectionmanager, variable) in tasks properties.
// Selected Connection Manager or Variable is available via variables that where
// filled by onchange events from the comboboxes.
this._taskHost.Properties["HasConnectionmanagerSource"].SetValue(this._taskHost, radConnectionSource.Checked);
if (radConnectionSource.Checked)
{
this._taskHost.Properties["SelectedVariableIDSource"].SetValue(this._taskHost, string.Empty);

if (this.cmbConnectionsSource.SelectedIndex != 0)
{
this._taskHost.Properties["SelectedConnectionManagerIDSource"].SetValue(this._taskHost, selectedConnectionManagerSource.ID);
}
else
{
this._taskHost.Properties["SelectedConnectionManagerIDSource"].SetValue(this._taskHost, string.Empty);
}
}
else
{
this._taskHost.Properties["SelectedConnectionManagerIDSource"].SetValue(this._taskHost, string.Empty);

if (this.cmbVariablesSource.SelectedIndex != 0)
{
this._taskHost.Properties["SelectedVariableIDSource"].SetValue(this._taskHost, selectedVariableSource.ID);
}
else
{
this._taskHost.Properties["SelectedVariableIDSource"].SetValue(this._taskHost, string.Empty);
}
}

// Close editor
this.DialogResult = DialogResult.OK;
this.Close();
}
#endregion

#region Onchange events methods Comboboxes
// Variable to store the selected connectionmanager. It will be filled by
// the SelectedIndexChange event and used by the OK button click event.
private ConnectionManager selectedConnectionManagerSource = null;

// Method to set the selected connectionmanager
private void cmbConnectionsSource_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox combobox = (ComboBox)sender;

// If <Choose connectionmanager> is selected then empty the textbox with the path
if (combobox.SelectedIndex == 0)
{
this.txtFilePathFinalSource.Text = "";
return;
}

// If <New connectionmanager> is selected then popup to create a new connection manager
if (combobox.SelectedIndex == 1)
{
int currentdIndex = -1;
IDtsConnectionService _dtsConnectionService = _serviceProvider.GetService(typeof(IDtsConnectionService)) as IDtsConnectionService;
System.Collections.ArrayList createdConnection = _dtsConnectionService.CreateConnection(Prompt.ShowConnectionManagerTypeDialog());
if (createdConnection.Count > 0)
{
ConnectionManager newConnectionManager = (ConnectionManager)createdConnection[0];
_dtsConnectionService.AddConnectionToPackage(newConnectionManager);
currentdIndex = combobox.Items.Add(newConnectionManager);
combobox.SelectedIndex = currentdIndex;
return;
}
else
{
// Cancel was clicked in popup
combobox.SelectedIndex = 0;
return;
}
}

// Fill the private variable to store the selected connectionmanager
selectedConnectionManagerSource = (ConnectionManager)combobox.SelectedItem;

// If the variable is still null then clear form
if (selectedConnectionManagerSource == null)
{
this.cmbConnectionsSource.SelectedIndex = 0;
this.txtFilePathFinalSource.Text = "";
return;
}

// Get the path of the connectionmanager. For Excel connectionmanagers
// you should use ExcelFilePath property instead of the connectionstring
if (selectedConnectionManagerSource.CreationName == "EXCEL")
{
this.txtFilePathFinalSource.Text = selectedConnectionManagerSource.Properties["ExcelFilePath"].GetValue(selectedConnectionManagerSource).ToString();
}
else
{
this.txtFilePathFinalSource.Text = selectedConnectionManagerSource.ConnectionString;
}
}

// Variable to store the selected variable. It will be filled by
// the SelectedIndexChange event and used by the OK button click event.
private Variable selectedVariableSource = null;

// Method to set the selected variable
private void cmbVariablesSource_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox combobox = (ComboBox)sender;

// If <Choose variable> is selected then empty the textbox with the path
if (combobox.SelectedIndex == 0)
{
this.txtFilePathFinalSource.Text = "";
return;
}

// If <New variable> is selected then popup to create a new variable
if (combobox.SelectedIndex == 1)
{
int currentdIndex = -1;
IDtsVariableService _dtsVariableService = _serviceProvider.GetService(typeof(IDtsVariableService)) as IDtsVariableService;
Variable newVariable = _dtsVariableService.PromptAndCreateVariable(this, null, "FilePath", "User", typeof(String));
if (newVariable != null)
{
currentdIndex = combobox.Items.Add(newVariable);
combobox.SelectedIndex = currentdIndex;
return;
}
else
{
// Cancel was clicked in popup
combobox.SelectedIndex = 0;
return;
}
}

// Fill the private variable to store the selected variable
selectedVariableSource = (Variable)combobox.SelectedItem;

// If the variable is still null then clear form
if (selectedVariableSource == null)
{
this.cmbVariablesSource.SelectedIndex = 0;
this.txtFilePathFinalSource.Text = "";
return;
}

// Show path in textbox
this.txtFilePathFinalSource.Text = (String)selectedVariableSource.Value;
}
#endregion

#region Radio buttons
private void radConnectionSource_CheckedChanged(object sender, EventArgs e)
{
// Enable/disable other fields when
// the radio button changes
RadioButton rad = (RadioButton)sender;

if (!rad.Checked)
{
this.cmbConnectionsSource.Enabled = false;
this.cmbVariablesSource.Enabled = true;
if (this.cmbVariablesSource.SelectedIndex != 0)
{
Variable var = ((Variable)this.cmbVariablesSource.SelectedItem);
if (var != null)
{
this.txtFilePathFinalSource.Text = ((Variable)this.cmbVariablesSource.SelectedItem).Value.ToString();
}
}
}
}

private void radVariableSource_CheckedChanged(object sender, EventArgs e)
{
// Enable/disable other fields when
// the radio button changes
RadioButton rad = (RadioButton)sender;
if (!rad.Checked)
{
this.cmbConnectionsSource.Enabled = true;
this.cmbVariablesSource.Enabled = false;
if (this.cmbConnectionsSource.SelectedIndex != 0)
{
ConnectionManager temp = ((ConnectionManager)this.cmbConnectionsSource.SelectedItem);
if (temp != null)
{
if (temp.CreationName == "EXCEL")
{
this.txtFilePathFinalSource.Text = temp.Properties["ExcelFilePath"].GetValue(temp).ToString();
}
else
{
this.txtFilePathFinalSource.Text = temp.ConnectionString;
}
}
}
}
}
#endregion

#region Methods to find Connectionmanager and Variable by id
private ConnectionManager FindConnectionManager(string connectionManagerID)
{
// This methods loops through all connection managers
// and returns the one that matches the GUID.
foreach (ConnectionManager connManager in this._connections)
{
if (connManager.ID == connectionManagerID)
{
return connManager;
}
}
return null;
}

private Variable FindVariable(string variableID)
{
// This methods loops through all variables
// and returns the one that matches the GUID.
foreach (Variable var in this._taskHost.Variables)
{
if (var.ID == variableID)
{
return var;
}
}
return null;
}
#endregion
}

#region ShowConnectionManagerTypeDialog
// A dialog for creating a new Connection Manager.
// You can choose between File, FLATFILE or EXCEL
// and then the corresponding dialog will open.

// This could also be done in a seperate windows
// form which is easier to format.
public static class Prompt
{
public static string ShowConnectionManagerTypeDialog()
{
Form prompt = new Form();
prompt.Width = 300;
prompt.Height = 200;
prompt.Text = "Choose Connection Manager type";
prompt.MaximizeBox = false;
prompt.MinimizeBox = false;
prompt.ControlBox = false;


RadioButton radConnectionManagerFile = new RadioButton() { Left = 50, Top = 20, Width = 200, Text = "Connection manager for files", Checked = true };
RadioButton radConnectionManagerFlatFile = new RadioButton() { Left = 50, Top = 50, Width = 200, Text = "Connection manager for flat files" };
RadioButton radConnectionManagerExcel = new RadioButton() { Left = 50, Top = 80, Width = 200, Text = "Connection manager for Excel files" };

Button confirmation = new Button() { Text = "Ok", Left = 150, Width = 100, Top = 110 };
confirmation.Click += (sender, e) => { prompt.Close(); };
prompt.Controls.Add(confirmation);
prompt.Controls.Add(radConnectionManagerFile);
prompt.Controls.Add(radConnectionManagerFlatFile);
prompt.Controls.Add(radConnectionManagerExcel);
prompt.Location = new Point(Screen.PrimaryScreen.WorkingArea.Width / 2 - prompt.Width / 2, Screen.PrimaryScreen.WorkingArea.Height / 2 - prompt.Height / 2);
prompt.ShowDialog();

string res = "";
if (radConnectionManagerExcel.Checked)
{
res = "EXCEL";
}
else if (radConnectionManagerFlatFile.Checked)
{
res = "FLATFILE";
}
else
{
res = "FILE";
}

return res;
}
}
#endregion
}



8) Get PublicKeyToken
For the other project you need the PublicKeyToken of the GUI assembly. So first build the GUI project and then, via the same command prompt of step 2, execute the following command in the BIN folder of your GUI project: sn.exe -T SSISJoost.myTaskUI.dll
Copy the number generated. You need it in the next project.
















9) The code for the actual work
This is the code from the project that does the actual work during runtime. See the comments for the explanation.
// C# code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Dts.Runtime;
using System.ComponentModel;
using System.Xml;
using System.IO;

namespace SSISJoost
{
// Connection to the editor assembly. Copy the PublicKeyToken from the previous step.
[DtsTask(
DisplayName = "My Task",
TaskType = "myTask",
TaskContact = "SSISJOOST Example",
IconResource = "SSISJoost.myTask.ico",
UITypeName = "SSISJoost.myTaskInterface, SSISJoost.myTaskUI, Version=1.0.0.0, Culture=Neutral,PublicKeyToken=80664248b6de6485",
RequiredProductLevel = DTSProductLevel.None)]
public class myTask : Task, IDTSComponentPersist
{

#region Get Set Properties
/*
* The properties of my task that will be
* saved in the XML of the SSIS package.
* You can add a Category and Description
* for each property making it clearer.
*/
private bool _hasConnectionmanagerSource = true;
[CategoryAttribute("my Task Source")]
[Description("True if a connectionmanager is used for the task as source. False if a variable is used.")]
public bool HasConnectionmanagerSource
{
get { return this._hasConnectionmanagerSource; }
set
{
this._hasConnectionmanagerSource = value;
}
}

private string _selectedConnectionManagerIDSource = "";
[CategoryAttribute("my Task Source")]
[Description("GUID of the selected source connectionmanager. If a variable is used, then 'SelectedVariableIDSource' is <null>")]
public string SelectedConnectionManagerIDSource
{
get { return this._selectedConnectionManagerIDSource; }
set { this._selectedConnectionManagerIDSource = value; }
}

private string _selectedVariableIDSource = "";
[CategoryAttribute("my Task Source")]
[Description("GUID of the selected source variable. If a variable is used, then 'SelectedConnectionManagerIDSource' is <null>")]
public string SelectedVariableIDSource
{
get { return this._selectedVariableIDSource; }
set { this._selectedVariableIDSource = value; }
}
#endregion

#region Version
public string Version
{
get
{
return System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).ToString();
}
}
#endregion

#region Variables for internal use
private ConnectionManager _selectedConnectionManagerSource = null;
private Variable _selectedVariableSource = null;
private string _filePathSource = "";
#endregion

#region Constructor
public myTask()
{

}
#endregion

#region Overrides for Task
public override void InitializeTask(Connections connections, VariableDispenser variableDispenser, IDTSInfoEvents events, IDTSLogging log, EventInfos eventInfos, LogEntryInfos logEntryInfos, ObjectReferenceTracker refTracker)
{
base.InitializeTask(connections, variableDispenser, events, log, eventInfos, logEntryInfos, refTracker);
}

public override DTSExecResult Validate(Connections connections, VariableDispenser variableDispenser, IDTSComponentEvents componentEvents, IDTSLogging log)
{

// If we have used a ConnectionManager then we check if we have a valid one !
if (HasConnectionmanagerSource)
{
try
{
// Check if a connection manager is selected in the combobox
if (String.IsNullOrEmpty(this._selectedConnectionManagerIDSource))
{
throw new ApplicationException("Connectionmanager is mandatory for source");
}

// Check if the selected connection manager still exists
if (FindConnectionManager(connections, _selectedConnectionManagerIDSource) == null)
{
throw new ApplicationException("Invalid connectionmanager for source");
}
}
catch (System.Exception e)
{
componentEvents.FireError(0, "My Task", "Invalid Source Connectionmanager.", "", 0);
return DTSExecResult.Failure;
}
}
else
// If we decided to use a Variable we at least must have chosen one !
{
if (String.IsNullOrEmpty(this._selectedVariableIDSource))
{
componentEvents.FireError(0, "My Task", "Selected Source Variable is mandatory", "", 0);
return DTSExecResult.Failure;
}
}


/*
* If you want to do some checks with the value from the connection manager or variable then
* you have to get that value. You can copy the code from the execute and then use that to
* validate the filepath from the connection manager or variable.
*/

return DTSExecResult.Success;
}

public override DTSExecResult Execute(Connections connections, VariableDispenser variableDispenser, IDTSComponentEvents componentEvents, IDTSLogging log, object transaction)
{

// Do the base Validation, if result is OK, then we continue
if (base.Execute(connections, variableDispenser, componentEvents, log, transaction) == DTSExecResult.Failure)
{
return DTSExecResult.Failure;
}

// Get the value from the connection manager or variable
if (HasConnectionmanagerSource)
{
_selectedConnectionManagerSource = FindConnectionManager(connections, _selectedConnectionManagerIDSource);
_filePathSource = GetFilePathSource(_selectedConnectionManagerSource);
}
else
{
_selectedVariableSource = FindVariable(variableDispenser, _selectedVariableIDSource);
_filePathSource = GetFilePathSource(_selectedVariableSource);
}

if (!File.Exists(_filePathSource))
{
componentEvents.FireError(0, "My Task", "File " + _filePathSource + " doesn't exist", "", 0);
return DTSExecResult.Failure;
}
else
{
return DTSExecResult.Success;
}
}
#endregion

#region Methods for IDTSComponentPersist
void IDTSComponentPersist.LoadFromXML(System.Xml.XmlElement node, IDTSInfoEvents infoEvents)
{
// This might occur if the task's XML has been modified outside of the Business Intelligence
// Or SQL Server Workbenches.
if (node.Name != "UnzipTask")
{
throw new Exception(string.Format("Unexpected task element when loading task - {0}.", "UnzipTask"));
}
else
{
// populate the private property variables with values from the DTS node.
this._hasConnectionmanagerSource = Convert.ToBoolean(node.Attributes.GetNamedItem("HasConnectionmanagerSource").Value);
this._selectedConnectionManagerIDSource = node.Attributes.GetNamedItem("SelectedConnectionManagerIDSource").Value;
this._selectedVariableIDSource = node.Attributes.GetNamedItem("SelectedVariableIDSource").Value;
}
}

void IDTSComponentPersist.SaveToXML(System.Xml.XmlDocument doc, IDTSInfoEvents infoEvents)
{
//create node in the package xml document
XmlElement taskElement = doc.CreateElement(string.Empty, "UnzipTask", string.Empty);

// create attributes in the node that represent the custom properties and add each to the element

// Boolean indicating if you are using a connection manager or variable
XmlAttribute UnzipTaskXmlAttribute = doc.CreateAttribute(string.Empty, "HasConnectionmanagerSource", string.Empty);
UnzipTaskXmlAttribute.Value = this._hasConnectionmanagerSource.ToString();
taskElement.Attributes.Append(UnzipTaskXmlAttribute);

// The GUID from the connection manager
UnzipTaskXmlAttribute = doc.CreateAttribute(string.Empty, "SelectedConnectionManagerIDSource", string.Empty);
UnzipTaskXmlAttribute.Value = this._selectedConnectionManagerIDSource.ToString();
taskElement.Attributes.Append(UnzipTaskXmlAttribute);

// The GUID from the variable
UnzipTaskXmlAttribute = doc.CreateAttribute(string.Empty, "SelectedVariableIDSource", string.Empty);
UnzipTaskXmlAttribute.Value = this._selectedVariableIDSource.ToString();
taskElement.Attributes.Append(UnzipTaskXmlAttribute);

//add the new element to the package document
doc.AppendChild(taskElement);
}
#endregion

#region CUSTOM Methods for getting path via Variable or Connectionmanager
private string GetFilePathSource(ConnectionManager selectedConnectionManager)
{
if (selectedConnectionManager != null)
{
if (selectedConnectionManager.CreationName == "EXCEL")
{
return selectedConnectionManager.Properties["ExcelFilePath"].GetValue(_selectedConnectionManagerSource).ToString();
}
else
{
return selectedConnectionManager.ConnectionString;
}
}
else
{
return "";
}
}

private string GetFilePathSource(Variable selectedVariable)
{
if (selectedVariable != null)
{
return (String)_selectedVariableSource.Value;
}
else
{
return "";
}
}

private Variable FindVariable(VariableDispenser variableDispenser, string variableID)
{
Variable tempVariable = null;
if (variableDispenser.Contains(_selectedVariableIDSource))
{
Variables vars = null;
variableDispenser.LockOneForRead(variableID, ref vars);

if (vars != null)
{
foreach (Variable var in vars)
{
if (var.ID == variableID)
{
tempVariable = var;
}
}
}
vars.Unlock();
}
return tempVariable;
}

private ConnectionManager FindConnectionManager(Connections connections, string connectionManagerID)
{
ConnectionManager tempConnManager = null;
foreach (ConnectionManager connManager in connections)
{
if (connManager.ID == connectionManagerID)
{
tempConnManager = connManager;
}
}
return tempConnManager;
}
#endregion
}
}

10) The Result
After building / deploying the solution, you need to close/reopen BIDS because the GAC is cached on startup. Now you can use your new task. For SSIS 2008 you need to right click the toolbox and click Choose Items. Then go to the Control Flow items and select your new task. For SSIS 2012 this is done automatically.
Your own custom SSIS task
























11) Download
You can download the complete Visual Studio 2010 example solutions:
SSIS 2008 version
SSIS 2012 version


Note: this is a very basic example. Use it as a base and check other examples like the one below.
More info: MSDNAndy Leonard, Matt Masson

Starting with BIML for SSIS

$
0
0
Yesterday I finally started using BIML in one of my projects. Very handy to create dozens of those simple but annoying packages in a fraction of the time you would normally need when creating them manually. No mistakes when creating your thirtieth staging package and all according to the naming conventions.

But two things bothered me and I think more people find them annoying:


1) Copy & Paste
When you copy and paste something from an other script, Visual Studio adds extra chars and the BIML Script doesn't work: Directive value without name.
<#@ template language="C#" hostspecific="true"#>
becomes

<#@ template="" language="C#" hostspecific="true"#>
Copy and paste error: adding =""













Directive value without name
















Solution
You van overcome this by changing the XML formatting options in Visual Studio. Go to the Tools menu and choose options. Then go to the Text Editor options, XML and formatting. There you must change auto format on paste from clipboard. Now try again pasting some BIML code. See this for more details.

disable on paste from clipboard


















2) Losing format & intellisense
When you mix the XML from BIML Script with C# code, the XML code obviously gets corrupt, because the brackets are not opening and closing one after the other:
<ExecutePackage  Name="EPT - <#=row["name"]#>">

And then, when you re-open the BIML Script, you have lost formatting and even worse you have lost intellisense.


Formatting and intellisense gone!

























Solution
You can overcome this by right clicking the BIML Script and select Open With... Then choose the XML (Text) Editor. Now it opens with formatting and intellisense. Setting it as the default editor didn't work.
Open in XML (Text) Editor















An alternative is using the online editor at http://www.bimlscript.com/Develop. This editor doesn't have the formatting issues and the <# and #> are formatted properly.




Next BIML blog post: Mixing BIML with .Net code

Mixing BIML with .Net code

$
0
0
Case
Recently I had to stage about 150 tables from a source database. I like creating SSIS packages, but not 150 times the same boring stage package. Is there an alternative?
Simplified version of my staging package (times 150)
















Solution
You can use BIML to create an SSIS package and when you combine that with some .Net code, you can easily repeat that for all you tables. For this example I want to copy the data from all database tables on my source server to my staging server. The tables are already created on my staging server and they have the exact same definition as my source server.

1) Install BIDS Helper
First install BIDS Helper which is an add-on for BIDS/SSDT. Then start BIDS/SSDT and create/open an SSIS project. Now you can right click the project and choose Add New Biml File. This will add a .biml file in the Miscellaneous folder.
Add New Biml File




















2) BIML Script
This is the basic BIML Script that creates one staging package for the color table. It has a truncate table command in an Execute SQL Task and a Data Flow Task to fill the table. See this for more examples.



















Truncate table Color














SELECT Code, Name FROM Color













Now you can right click the BIML Script and generate the SSIS color staging package. It will automatically appear in the SSIS project.

Right Click and choose Generate SSIS packages



















3) Adding .Net code
By adding some .Net code to your BIML code, you can create a more dynamic script. For this example I will use C# code, but you can translate it to VB.Net if you prefer that language. You can add .Net code between <# and #>, but note that adding that to BIML code could mess up the formatting within Visual Studio. It's even worse to show it on a webpage. So see screenshot and then download the code.
Screenshot, because the mixed BIML and C# code isn't readable in HTML
























Download Biml Script here.

Now you can right click the BIML Script and generate the SSIS staging packages for all source tables.

4) Master package
Now you need a master package for all the new staging packages. You can use a Foreach Loop in your master package to loop through all child packages. Or you can use BIML to create the master package:
Master package example 1: loop through SSISDB













Download Biml Script here
Master package 2: loop through project folder on filesystem














Download Biml Script here

BIML: An error occurred while parsing EntityName

$
0
0
Case
I'm creating an SSIS package with BIML and I want to add an expression in my Derived Column with an Logical AND &&.


ISNULL([Column1]) && ISNULL([Column2])


But when I check the BIML Script for errors with the 'Check Biml for Errors'-option in the context menu, I got an error: An error occurred while parsing EntityName
An error occurred while parsing EntityName














When I replace it with an Logical Or || it works without errors. What's wrong?

Solution
An XML document doesn't like an ampersand (&). You have to replace it by &amp; or enclose it with CDATA.



ISNULL([Column1]) &amp;&amp; ISNULL([Column2])





ISNULL([Column1]) <![CDATA[&&]]> ISNULL([Column2])




Now you can build the Biml Script without errors.

Custom SSIS Component: Multiple Expressions Task

$
0
0
To change the value of a variable during runtime you need an expression or a Script Task. The expression isn't very flexible and the Script Task requires .Net knowledge. That's why Microsoft introduced the Expression Task in SSIS 2012. It enables you to set the value of one variable during runtime. Downside is that it isn't available for 2008 and you can only set one variable at a time.

Multiple Expressions Task







We, me and my colleague Marc Potters, have created a new cool task to set the values of multiple variables in a single task. You can a either use a value or the Expression Builder.
Enter a value or use an expression


Or use the SSIS Expression Builder

























You can also create variables within the task via a context menu.
Create variables














Other features of version 0.1:
- Clearly shows faulty expressions, but you can close the editor without losing it.
- Create new rows to set the value of existing variables.
- Delete rows via the context menu
- Indicator to show that an expression is used


Disclaimer
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Downloads:
You can download a SSIS 2008 and 2012 version on the download page.

Installation
The installer registers the DLL in the GAC and copies it to the task folder of SSIS (example: C:\Program Files\Microsoft SQL Server\100\DTS\Tasks\). After that you have to close Visual Studio/BIDS because it caches the GAC on startup. Restarting SQL Server Data Tools is not necessary.

How add the task the the toolbox (2008 only)
When you open a SSIS project in Visual Studio/Bids, right click the toolbox and choose "Choose Items...". After this Visual Studio will be busy for a couple of seconds!
Right click toolbox






















When the Choose Toolbox Items window appears, go to the SSIS Control Flow Items and search for the newly installed Multiple Expressions Task and select it. Click ok to finish.
Choose Toolbox Items





















Now the new task will appear in the toolbox. Ready to use! Have fun.
New task added























Bugs or feature suggestions
Please contact me or leave a comment if you have any suggestions or found a bug in this Custom task.




Sending mail within SSIS - Part 3: Execute SQL Task

$
0
0
Case
I want to send mail within SSIS, preferably HTML formatted. What are the options?

Solutions
There are a couple of solutions to mail within SSIS:
  1. Send Mail Task
  2. Script Task with SmtpClient Class
  3. Execute SQL Task with sp_send_dbmail
  4. Custom Tasks like Send HTML Mail Task or COZYROC

To demonstrate the various solutions, I'm working with these four SSIS string variables. They contain the subject, body, from- and to address. Add these variables to your package and give them a suitable value. You could also use parameters instead if you're using 2012 project deployment.
Add these four variables to your package










A) Execute SQL Task
The Execute SQL Task solution uses a stored procedure from SQL Server. To use that you first have to configure database mail in SSMS.

1) Database Mail Wizard
Open SQL Server Management Studio (SSMS). Go to Management and then to Database Mail.
Database Mail


















2) Enable Database Mail
If Database Mail isn't available it will ask for it. Choose the first option to create a profile.
Enable Database Mail and create profile


















3) Create Profile
Enter a name and description for the mail profile. You will need the name in the stored procedure later on.
Create a mail profile


















4) New Database Mail Account
Click the Add button to create a new database mail account. This is where you configure the SMTP server and the FROM address.

Configure SMTP and FROM address

















Account ready, continue wizard


















5) Public / Private
Make your profile public (or private)
Public profile


















6) System Parameters
Configure the System Parameters like max attachment size.
Configure System Parameters


















7)  Finish wizard
Now finish the wizard and go back to SSIS / SSDT.
Finish

Close
































8) Add OLE DB Connection Manager
Add an OLE DB Connection Manager and connect to the server where you configured DatabaseMail.
OLE DB Connection Manager


























9) Add Execute SQL Task
Add an Execute SQL Task to the Control Flow or an Event Handler. Edit it and select the new connection manager. In the SQLStatement field we are executing the sp_send_dbmail stored procedure with some parameters to get the, subject, body and from address from the SSIS variables.

' Stored Procedure with parameters
EXEC msdb.dbo.sp_send_dbmail
@profile_name = 'SSIS Joost Mail Profile',
@recipients = ?,
@subject = ?,
@body = ?,
@body_format = 'HTML' ;

sp_send_dbmail in SSIS




















10) Parameters
Go to the Parameter Mapping pane and add the SSIS string variables as parameters. We can't configure the FROM address because we did that already in SSMS (Step 4).
Parameters























11) The result
Now execute the Execute SQL Task and watch your mailbox.

An email with html formatting






















If you don't like this solution, check out the Script Task solution or the third party tasks.

Sending mail within SSIS - Part 2: Script Task

$
0
0
Case
I want to send mail within SSIS, preferably HTML formatted. What are the options?

Solutions
There are a couple of solutions to mail within SSIS:
  1. Send Mail Task
  2. Script Task with SmtpClient Class
  3. Execute SQL Task with sp_send_dbmail
  4. Custom Tasks like Send HTML Mail Task or COZYROC

To demonstrate the various solutions, I'm working with these four SSIS string variables. They contain the subject, body, from- and to address. Add these variables to your package and give them a suitable value. You could also use parameters instead if you're using 2012 project deployment.
Add these four variables to your package










A) Script Task
The Script Task is a little harder to use than the Send Mail Task (if you don't have .Net knowledge), but it doesn't have the same drawbacks as the Send Mail Task. It uses the SmtpClient Class and it has an HTML formatted mail option and a configurable port number.

1) SMTP Connection Manager
This first step is optional. I like to use as much as possible standard connection managers. Right click in the Connection Managers window and add an SMTP Connect manager. Add the SMTP server and change other options if necessary (other options are not used in this example). The alternative is to use an extra SSIS string variable for storing the SMTP Server.
SMTP Connection manager
















2) Add Script Task
Add a Script Task to your Control Flow (or one of the event handlers). Give it a suitable name and add the SSIS variables as readonly variables to the Script Task.
Add all SSIS variables as ReadOnly





















3) The Script
In the Scipt I'm using the variables and the connection manager to fill the properties of the SMTP client. Copy the contents of the Main method to your method and add the extra import/using on top.
// C# Code
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
using System.Net.Mail; // Added

namespace ST_df6618207373422d961b80ca8b6a56e2
{
[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
public void Main()
{
// Storing SSIS variables in .Net variables. You could skip this step and call the SSIS variables in the actual mail code
// to reduce the number of code lines. Or you could fill these .Net variables with hardcoded values.
String SendMailFrom = Dts.Variables["SendMailFrom"].Value.ToString();
String SendMailTo = Dts.Variables["SendMailTo"].Value.ToString();
String SendMailSubject = Dts.Variables["SendMailSubject"].Value.ToString();
String SendMailBody = Dts.Variables["SendMailBody"].Value.ToString();

// Get SMTP Server from SMTP Connection Manager. Alternative is to use extra variables or paramters instead:
// String SmtpServer = Dts.Variables["SmtpServer"].Value.ToString();
String SmtpServer = Dts.Connections["My SMTP Connection Manager"].Properties["SmtpServer"].GetValue(Dts.Connections["My SMTP Connection Manager"]).ToString();

// Create an email and change the format to HTML
MailMessage myHtmlFormattedMail = new MailMessage(SendMailFrom, SendMailTo, SendMailSubject, SendMailBody);
myHtmlFormattedMail.IsBodyHtml = true;

// Create a SMTP client to send the email
SmtpClient mySmtpClient = new SmtpClient(SmtpServer);
mySmtpClient.Port = 2525; // If you want to use a different portnumber instead of the default. Else remove this line.
mySmtpClient.Send(myHtmlFormattedMail);

// Close Script Task with success
Dts.TaskResult = (int)ScriptResults.Success;
}

#region ScriptResults declaration

enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
#endregion
}
}

or use VB.Net code

' VB.Net Code
Imports System
Imports System.Data
Imports System.Math
Imports Microsoft.SqlServer.Dts.Runtime
Imports System.Net.Mail ' Added

<Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute()> _
<System.CLSCompliantAttribute(False)> _
Partial Public Class ScriptMain
Inherits Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase

Public Sub Main()
' Storing SSIS variables in .Net variables. You could skip this step and call the SSIS variables in the actual mail code
' to reduce the number of code lines. Or you could fill these .Net variables with hardcoded values.
Dim SendMailFrom As String = Dts.Variables("SendMailFrom").Value.ToString()
Dim SendMailTo As String = Dts.Variables("SendMailTo").Value.ToString()
Dim SendMailSubject As String = Dts.Variables("SendMailSubject").Value.ToString()
Dim SendMailBody As String = Dts.Variables("SendMailBody").Value.ToString()

' Get SMTP Server from SMTP Connection Manager. Alternative is to use extra variables or paramters instead:
' Dim SmtpServer as String = Dts.Variables("SmtpServer").Value.ToString();
Dim SmtpServer As String = Dts.Connections("My SMTP Connection Manager").Properties("SmtpServer").GetValue(Dts.Connections("My SMTP Connection Manager")).ToString()

' Create an email and change the format to HTML
Dim myHtmlFormattedMail As New MailMessage(SendMailFrom, SendMailTo, SendMailSubject, SendMailBody)
myHtmlFormattedMail.IsBodyHtml = True

' Create a SMTP client to send the email
Dim mySmtpClient As New SmtpClient(SmtpServer)
mySmtpClient.Port = 2525 ' If you want to use a different portnumber instead of the default. Else remove this line.
mySmtpClient.Send(myHtmlFormattedMail)

' Close Script Task with success

Dts.TaskResult = ScriptResults.Success
End Sub

#Region "ScriptResults declaration"
'This enum provides a convenient shorthand within the scope of this class for setting the
'result of the script.

'This code was generated automatically.
Enum ScriptResults
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
End Enum

#End Region

End Class

4) The result
Close the Script Task and execute it: An email message with html formatting!
HTML formatted email





















If you're not into .Net, but really like TSQL then check out the next solution: Execute SQL Task with sp_send_dbmail

Sending mail within SSIS - Part 1: Send Mail Task

$
0
0
Case
I want to send mail within SSIS, preferably HTML formatted. What are the options?

Solutions
There are a couple of solutions to mail within SSIS:
  1. Send Mail Task
  2. Script Task with SmtpClient Class
  3. Execute SQL Task with sp_send_dbmail
  4. Custom Tasks like Send HTML Mail Task or COZYROC

To demonstrate the various solutions, I'm working with these four SSIS string variables. They contain the subject, body, from- and to address. Add these variables to your package and give them a suitable value. You could also use parameters instead if you're using 2012 project deployment.
Add these four variables to your package










A) Send Mail Task
This is the standard task within SSIS to send mail. Good for simple plaintext emails but there are a couple of drawbacks. First see how it works.

1) SMTP Connection Manager
Right click in the Connection Managers window and add an SMTP Connect manager. Add the SMTP server. This is the first drawback. The settings are very limited. Things like port or credentials can't be set.
SMTP Connection manager















2) Send Mail Task
Add the Send Mail Task to the Control Flow (or to an event handler). Give it a suitable name and on the Mail pane at SmtpConnection, select our new Connection Manager.
Select SMTP Connection Manager























3) Expressions
After selecting the Connection Manager (leave rest unchanged) go to the Expressions pane and add an expression on the subject, body, to and from. Use the variables to overwrite these fields. After this click OK to close the editor and test it.
Expressions




















4) Testing
Now run the task and check the mailbox. Now you will see the second drawback. The Send Mail Task doesn't support HTML formatted mail. So only plain text.
No HTML Formatting





















If you want to overcome the two drawbacks then you have to use one of the other solutions. Next example solution: Script Task

Use Visual Studio 2012 for SSIS 2012

$
0
0
Case
I want to use Visual Studio 2012 to edit my SSIS packages, but during installation of Microsoft SQL Server Data Tools - Business Intelligence for Visual Studio 2012,  I get an error:
Rule "Same architecture installation" failed. The CPU architecture of installing feature(s) is different than the instance specified. To continue, add features to this instance with the same architecture.
The CPU architecture of installing feature(s) is different than the instance specified. To continue, add features to this instance with the same architecture.
The CPU architecture of installing feature(s) is different
than the instance specified. To continue, add features
to this instance with the same architecture



















Solution
During installation you selected the wrong Installation Type. Although it might not sound logical, you should perform a new installation instead of adding features to the existing instance.
Do NOT add features to an existing instance





















For the sake of completeness, lets review all steps.

1) Download
First download Microsoft SQL Server Data Tools - Business Intelligence for Visual Studio 2012

2) Start installation
Start the installer and include any updates.























3) Installation type
Now the important one. Use the default installation type: Perform a new installation of SQL Server 2012.
Perform a new installation of SQL Server 2012

License






































4) Features
Select all features and then, Next, next...
Select all features (at least the first)


No errors or warnings this time





















5) Finish
Finish the installation and start SQL Server Data Tools (SSDT) for Visual Studio 2012. Now you will see the familiar BI project types.
Finished

New: the BI project templates



































6) Color themes
When you install Visual Studio 2012 Update 3 you will get an extra blue color theme which is less depressing then the light grey and dark grey color themes. Or use the Visual Studio 2012 Color Theme Editor for even more color themes.

The color themes of Visual Studio 2012








Create your own custom connection manager

$
0
0
Case
I want to create my own custom SSIS Connection Manager with a GUI. How do you do that?

Solution
A custom connection manager is only for storing properties from a connection. You also need to create custom task or transformation to use those connection manager properties. For this example I will create a very basic connection manager which you can extend for your own needs. It stores a URL, a password and an username. The Connection Manager is for SSIS 2008 and 2012 and I will use Visual Studio 2010 to create it. Programming language is C#. Use this page to translate the code to VB.Net if you prefer that language.
My first SSIS Connection Manager
















1) Create Visual Studio project
For my connection manager I used two C# Class Library projects. One for the GUI/editor and one for the code. For SSIS 2008 I will use .Net framework 3.5 and for SSIS 2012 I will use .Net framework 4.0
Two projects for my Connection Manager



















2) Create key for strongname
You need to strongname your DLL's so that SSIS can use them. More about that in this Codeguru article: Giving a .NET Assembly a Strong Name. Open the Visual Studio 2010 Command Prompt (in Windows start menu). Browse to your project folder and execute the following command to create a key file: sn.exe -k myConnectionManager.snk

Microsoft (R) .NET Framework Strong Name Utility


















3) Add key to project
The key file should be added to both projects.
Add key to projects



















And after adding them, you need to sign the projects. Go to the properties of the projects and then to the Signing page. There you can sign the assembly with your newly generated key. Do this for both projects.
Sign Assembly



















4) Adding SSIS reference
We need to add references to SSIS libraries. The GUI project needs two references:
  • Microsoft.SqlServer.Dts.Design
  • Microsoft.SQLServer.ManagedDTS
And the code project only needs one reference:
  • Microsoft.SQLServer.ManagedDTS

For SSIS 2008 they can be found in the program files folder. Something like:
C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SQLServer.ManagedDTS.dll
And for SSIS 2012 they are located in the GAC. Something like:
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.SqlServer.ManagedDTS\v4.0_11.0.0.0__89845dcd8080cc91\Microsoft.SqlServer.ManagedDTS.dll
Add references


















5) Build Events
To use the connection managers dll's in SSIS you need to copy them to the GAC and to the connections folder of SSIS. With the Build Events you can do that automatically when you build the visual studio project. Go to the properties of your projects and then to the Build Events. Add the following command to the Post-Build events.

2008
cd $(ProjectDir)
@SET CONNECTIONSDIR="C:\Program Files (x86)\Microsoft SQL Server\100\DTS\Connections\"
@SET CONNECTIONSDIR64="C:\Program Files\Microsoft SQL Server\100\DTS\Connections\"
@SET GACUTIL="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\gacutil.exe"

Echo Installing dll in GAC
Echo $(OutDir)
Echo $(TargetFileName)
%GACUTIL% -if "$(OutDir)$(TargetFileName)"

Echo Copying files to Connections 32bit
copy "$(OutDir)$(TargetFileName)" %
CONNECTIONSDIR%
Echo Copying files to Connections 64bit
copy "$(OutDir)$(TargetFileName)" %CONNECTIONSDIR64%

2012
cd $(ProjectDir)
@SET CONNECTIONSDIR="C:\Program Files (x86)\Microsoft SQL Server\110\DTS\Connections\"
@SET CONNECTIONSDIR64="C:\Program Files\Microsoft SQL Server\110\DTS\Connections\"
@SET GACUTIL="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe"

Echo Installing dll in GAC
Echo $(OutDir)
Echo $(TargetFileName)
%GACUTIL% -if "$(OutDir)$(TargetFileName)"

Echo Copying files to Connections 32bit
copy "$(OutDir)$(TargetFileName)" %CONNECTIONSDIR
%

Echo Copying files to Connections 64bit
copy "$(OutDir)$(TargetFileName)" %
CONNECTIONSDIR64%
Post-build events


















Make sure you run Visual Studio as Administrator. Otherwise the build events won't work.

6) Icons
I have added the same icon file to both projects. In the properties of the icon file you have to set Build Action to "Embedded Resource". In the UI project you can use the icon in your Windows Form. This will show when you edit the connection manager.
Icon for editor

















The other project has an attribute called IconResource which gives Tasks and Transformations there custom icon (see step 9). It should be filled with the assembly name and the icon name. In my project it's "SSISJoost.myConnectionManager" + ".myConnectionManager.ico". Although this is a valid property according to msdn, it doesn't seem to work for Connection Managers. I will post an update if there is a solution or workaround available.


7) Gui project code
To keep everything clear and easy to explain I created a simple connection manager. In the GUI you can add a URL, UserName and password. The password will be a sensitive field so that it wont show in the source code. See the code comments for the explanation.

myConnectionManagerInterface.cs
// C# code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.SqlServer.Dts.Design;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Runtime.Design;

// Interface for connection editor
namespace SSISJoost
{
public class myConnectionManagerInterface : IDtsConnectionManagerUI
{
private ConnectionManager _connectionManager;
private IServiceProvider _serviceProvider;

public myConnectionManagerInterface()
{

}

public void Initialize(ConnectionManager connectionManager, IServiceProvider serviceProvider)
{
this._connectionManager = connectionManager;
this._serviceProvider = serviceProvider;
}


public ContainerControl GetView()
{
myConnectionManagerEditor editor = new myConnectionManagerEditor();
editor.ConnectionManager = this._connectionManager;
editor.ServiceProvider = this._serviceProvider;
return editor;
}

public void Delete(IWin32Window parentWindow)
{
}

public void New(IWin32Window parentWindow)
{
}

public bool Edit(System.Windows.Forms.IWin32Window parentWindow, Microsoft.SqlServer.Dts.Runtime.Connections connections, Microsoft.SqlServer.Dts.Runtime.Design.ConnectionManagerUIArgs connectionUIArg)
{
myConnectionManagerEditor editor = new myConnectionManagerEditor();

editor.Initialize(_connectionManager, this._serviceProvider);
if (editor.ShowDialog(parentWindow) == DialogResult.OK)
{
editor.Dispose();
return true;
}
else
{
editor.Dispose();
return false;
}
}

public bool New(System.Windows.Forms.IWin32Window parentWindow, Microsoft.SqlServer.Dts.Runtime.Connections connections, Microsoft.SqlServer.Dts.Runtime.Design.ConnectionManagerUIArgs connectionUIArg)
{
myConnectionManagerEditor editor = new myConnectionManagerEditor();

editor.Initialize(_connectionManager, this._serviceProvider);
if (editor.ShowDialog(parentWindow) == DialogResult.OK)
{
editor.Dispose();
return true;
}
else
{
editor.Dispose();
return false;
}
}
}
}

myConnectionManagerEditor.cs (code behind from windows form)
// C# code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.SqlServer.Dts.Design;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Runtime.Design;


namespace SSISJoost
{
public partial class myConnectionManagerEditor : Form
{
#region General Connection Manager Methods
// Setting and getting ConnectionManager
private ConnectionManager _connectionManager;
public ConnectionManager ConnectionManager
{
get { return _connectionManager; }
set { _connectionManager = value; }
}

// Setting and getting ServiceProvider
private IServiceProvider _serviceProvider = null;
public IServiceProvider ServiceProvider
{
get { return _serviceProvider; }
set { _serviceProvider = value; }
}

// Default constructor
public myConnectionManagerEditor()
{
InitializeComponent();
}

public void Initialize(ConnectionManager connectionManager, IServiceProvider serviceProvider)
{
this._connectionManager = connectionManager;
this._serviceProvider = serviceProvider;
}
#endregion

#region Page Load
// Fill the fields of the form. Get data from connectionManager object
private void SMTP2Editor_Load(object sender, EventArgs e)
{
this.txtName.Text = this._connectionManager.Name;
this.txtDescription.Text = this._connectionManager.Description;
this.txtURL.Text = this._connectionManager.Properties["URL"].GetValue(_connectionManager).ToString();
this.txtUserName.Text = this._connectionManager.Properties["UserName"].GetValue(_connectionManager).ToString();
this.txtPassword.Text = this._connectionManager.Properties["Password"].GetValue(_connectionManager).ToString();
}
#endregion

#region Buttons
// Save value from fields in connectionManager object
private void btnOK_Click(object sender, EventArgs e)
{
this._connectionManager.Name = this.txtName.Text;
this._connectionManager.Description = this.txtDescription.Text;
this._connectionManager.Properties["URL"].SetValue(this._connectionManager, this.txtURL.Text);
this._connectionManager.Properties["UserName"].SetValue(this._connectionManager, this.txtUserName.Text);
this._connectionManager.Properties["Password"].SetValue(this._connectionManager, this.txtPassword.Text);
this.DialogResult = DialogResult.OK;
}

// Cancel diolog
private void btnCancel_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
}

// Show some helpful information
private void btnHelp_Click(object sender, EventArgs e)
{
MessageBox.Show("Help");
}
#endregion
}
}



8) Get PublicKeyToken
For the other project you need the PublicKeyToken of the GUI assembly. So first build the GUI project and then, via the same command prompt of step 2, execute the following command in the BIN folder of your GUI project: sn.exe -T SSISJoost.myConnectionManagerUI.dll
Copy the number generated. You need it in the next project.


















9) The code for the actual work
This is the code from the project that does the actual storing of the properties in the package XML. See the comments for the explanation.
// C# code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.SqlServer.Dts.Runtime;

namespace SSISJoost
{
// Connection to the editor assembly. Copy the PublicKeyToken from the previous step.
[DtsConnection(
ConnectionType = "myConnectionManager",
DisplayName = "My Connection Manger",
ConnectionContact = "SSISJoost",
Description = "My Custom Connection manager",
//IconResource = "SSISJoost.myConnectionManager.myConnectionManager.ico", // Bug in SSIS => No custom icons for Connection Managers
UITypeName = "SSISJoost.myConnectionManagerInterface,SSISJoost.myConnectionManagerUI,Version=1.0.0.0,Culture=neutral,PublicKeyToken=80664248b6de6485")
]
public class myConnectionManager : ConnectionManagerBase, IDTSComponentPersist
{
#region Variables for internal use
// The template for the connectionstring, but without the sensitive password property
private const string CONNECTIONSTRING_TEMPLATE = "URL=<URL>;UserName=<UserName>;";
#endregion

#region Get Set Properties
/*
* The properties of my connection manager that
* will be saved in the XML of the SSIS package.
* You can add a Category and Description
* for each property making it clearer.
*/
private string _connectionString = String.Empty;
public override string ConnectionString
{
get
{
UpdateConnectionString();
return _connectionString;
}
//connectionstring is now readonly
//set
//{
// _connectionString = value;
//}
}

private string _url = String.Empty;
[CategoryAttribute("my connection manager")]
[Description("Some URL to do something with in an other task or transformation")]
public string URL
{
get { return this._url; }
set { this._url = value; }
}

private string _userName = String.Empty;
[CategoryAttribute("my connection manager")]
[Description("The username needed to access the url")]
public string UserName
{
get { return this._userName; }
set { this._userName = value; }
}

// Notice the password property to hide the chars
private string _password = String.Empty;
[CategoryAttribute("my connection manager")]
[Description("The secret password")]
[PasswordPropertyText(true)]
public string Password
{
get { return this._password; }
set { this._password = value; }
}
#endregion

#region Overriden methods
public override object AcquireConnection(object txn)
{
// Set the connectionstring
UpdateConnectionString();
return base.AcquireConnection(txn);
}

public override void ReleaseConnection(object connection)
{
base.ReleaseConnection(connection);
}

public override DTSExecResult Validate(IDTSInfoEvents infoEvents)
{
// Very basic validation example:
// Check if the URL field is filled.
// Note: this is a runtime validation
// In the form you can add some more
// designtime validation.
if (string.IsNullOrEmpty(_url))
{
infoEvents.FireError(0, "My Custom Connection Manager", "URL is mandatory.", string.Empty, 0);
return DTSExecResult.Failure;
}
else
{
return DTSExecResult.Success;
}
}
#endregion

#region Update ConnectionString
private void UpdateConnectionString()
{
// Create a connectionstring, but without sensitive properties like the password
String connectionString = CONNECTIONSTRING_TEMPLATE;

connectionString = connectionString.Replace("<URL>", URL);
connectionString = connectionString.Replace("<UserName>", UserName);

_connectionString = connectionString;
}
#endregion

#region Methods for IDTSComponentPersist
// These two methods are for saving the data in the package XML without showing sensitive data
void IDTSComponentPersist.LoadFromXML(System.Xml.XmlElement node, IDTSInfoEvents infoEvents)
{
// Checking if XML is correct. This might occur if the connection manager XML has been modified outside BIDS/SSDT
if (node.Name != "MYCONNECTIONMANAGER")
{
throw new Exception(string.Format("Unexpected connectionmanager element when loading task - {0}.", "MYCONNECTIONMANAGER"));
}
else
{
// Fill properties with values from package XML
this._userName = node.Attributes.GetNamedItem("UserName").Value;
this._url = node.Attributes.GetNamedItem("URL").Value;


foreach (XmlNode childNode in node.ChildNodes)
{
if (childNode.Name == "Password")
{
this._password = childNode.InnerText;
}
}
this._connectionString = node.Attributes.GetNamedItem("ConnectionString").Value;
}
}

void IDTSComponentPersist.SaveToXML(System.Xml.XmlDocument doc, IDTSInfoEvents infoEvents)
{
XmlElement rootElement = doc.CreateElement("MYCONNECTIONMANAGER");
doc.AppendChild(rootElement);

XmlAttribute connectionStringAttr = doc.CreateAttribute("ConnectionString");
connectionStringAttr.Value = _connectionString;
rootElement.Attributes.Append(connectionStringAttr);

XmlAttribute userNameStringAttr = doc.CreateAttribute("UserName");
userNameStringAttr.Value = _userName;
rootElement.Attributes.Append(userNameStringAttr);

XmlAttribute urlStringAttr = doc.CreateAttribute("URL");
urlStringAttr.Value = _url;
rootElement.Attributes.Append(urlStringAttr);

if (!string.IsNullOrEmpty(_password))
{
XmlElement passwordElement = doc.CreateElement("Password");
rootElement.AppendChild(passwordElement);
passwordElement.InnerText = _password;

// This will make the password property sensitive
XmlAttribute passwordAttr = doc.CreateAttribute("Sensitive");
passwordAttr.Value = "1";
passwordElement.Attributes.Append(passwordAttr);
}
}
#endregion
}
}

10) The Result
After building / deploying the solution, you need to close/reopen BIDS/SSDT because the GAC is cached on startup. Now you can use your new connection manager. But without any custom tasks or transformations it, it's basically useless... You could use a Script Task to get the connectionstring or a certain property.


11) Download
You can download the complete Visual Studio 2010 example solutions:
SSIS 2008 version
SSIS 2012 version

Note: this is a very basic example. Use it as a base and check other examples like the one below.
More info: MSDNKenR, Matt Masson (twice)

Custom Task in BIML Script

$
0
0
Case
I want to add a custom task to my BIML Script. How do you do that in BIML?

Solution

1) Intro
For BIML you first need to install BIDS Helper. At the time of writing the current version of BIDS Helper is 1.6.4. For this example I assume you have basic experience with writing BIML Script and you have installed the Custom Task you want to use in the BIML Script.

2) Create example package
Create an example package with the Custom Task. You need to copy some of the XML code later on. For this example I will use my own custom ZipTask. Two string variables are used for storing the filepath of the sourcefile (that will be zipped) and filepath of the zipfile.
Custom Task with to variables











3) BIML Script Variables
The ZipTask uses two variables and it stores the GUID of those variables in its properties. When I create the variables in the BIML Script I use the same GUID's as in my example package. To get those GUID's click on the variable and go to its properties. Or look it up in the xml code of the example package by right clicking the package in the solution explorer and choose View Code.
BIML Script with two variables with GUID's














4) BIML Script CustomTask
The BIML Script for a custom task is:




 
You can lookup the value of the CustomTask properties in the XML code of your example package. Search for the DTS:CreationName and DTS:TaskContact properties of your Custom Task. Then copy and paste the exact value to the corresponding property in the BIML Script.
Lookup property values in xml code example package























5) ObjectData
Now we need to fill the ObjectData tag. Go back to the XML code of your example package and search for the ObjectData tag of your custom task. Copy the contents (everything between <DTS:ObjectData and </DTS:ObjectData>) to an advanced text editor like Notepad++ where you can replace the following codes
<    by   &lt;
>    by   &gt;
\r\n by               (Carriage Return + Line Feed by a Space)

Now copy all that code from your text editor to the ObjectData tag within your BIML Script. This text contains all the properties of the custom task including the guid of the two variables.
ObjectData






















6) Finish
Now you're ready to generate the package with your custom task.
The Result

Execute multiple child packages in parallel with loop

$
0
0
Case
I used a foreach loop to execute all my staging packages, but my server isn't using all resources. Is there a way to execute multiple child packages at once, but with a loop?

Executing packages sequentially






















Solution
The trick is to use a queue of packages and to have multiple 'processes' taking packages from the queue to execute them. The number of packages that can be executed at a time depends on the complexity of your packages. Staging packages for a single source are not complex, so a good guideline/starting point is to execute one package per processor core.


4 cores => execute 4 child packages at the same time
















1) The Queue
For this example I will use a FIFO (first in, first out) queue that is stored in a database table. Alternatives could be the Windows Message Queue or the SQL Service Broker.
-- Create QUEUE table
CREATE TABLE [dbo].[FifoPackageQueue](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Package] [varchar](50) NULL
) ON [PRIMARY]

GO

-- Add Index on Id
CREATE CLUSTERED INDEX cdxFifoPackageQueue on FifoPackageQueue (Id)

GO

-- Log tabel
CREATE TABLE [dbo].[FifoPackageQueueLog](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Package] [varchar](50) NULL,
[StartTime] [datetime] NULL,
[EndTime] [datetime] NULL
) ON [PRIMARY]

GO


2) Variables and Parameter
For each 'execution line' in my control flow I need one SSIS string variable to store the package name. And I use one Package Parameter to indicate how many execution lines will be active. You can also use an integer variable for that if your SSIS version doesn't have parameters.

String variables, one per execution line









Integer Package Parameter.





3) Fill Queue
In this example I will use an INSERT INTO SELECT query to get all staging packages from SSISDB. You could also use regular INSERT queries or even a Foreach Loop or Script Task that loops through a folder to add packages from the file system to the queue.
INSERT INTO CONV_LOG..FifoPackageQueue
(
[Package]
)
SELECT Packages.[name]
FROM [SSISDB].[internal].[packages] as Packages
INNER JOIN [SSISDB].[internal].[projects] as Projects
on Packages.project_version_lsn = Projects.object_version_lsn
WHERE Projects.name = 'MyProject'
AND Packages.name like 'STG%';


4) Execution Lines
Each Execution line starts with a Sequence Container connected to the Fill queue task. The Precedence Constraint Expression is @[$Package::ParallelProcesses] >= 1 for the first and @[$Package::ParallelProcesses] >= 2 for the second and so on.
Expression to limit the number of parallel executions


















5) Get first package from queue
The Execute SQL Task gets the first package name from the queue and stores it in the SSIS string variable. If the variable is empty then it doesn't continue to the next For Loop.
Output clause is available in SQL 2005





















set nocount on;

-- Declare temporary table to store the result of the delete
DECLARE @TmpTable TABLE (Package varchar(50));
-- Declare integer variable for counting the delete result
DECLARE @PackageCount int;

-- Select first record, lock it, delete it and store name in temporary table
WITH cte as (
SELECT top(1) Package
FROM FifoPackageQueue WITH (rowlock, readpast)
ORDER BY Id
)
DELETE FROM cte
output deleted.Package INTO @TmpTable

-- Check if there is 1 record in temporary table
SELECT @PackageCount = count(*) FROM @TmpTable
if @PackageCount = 1
BEGIN
-- Return package name
SELECT Package FROM @TmpTable
END
ELSE
BEGIN
-- Temporary table was empty so queue was empty
-- Return empty string to stop next precedence constraint
SELECT '' as Package
END


Store package name in SSIS variable
























6) For Loop
The For Loop loops until the package name is empty.
Loop until empty























7) Log starttime
The first Execute SQL Task in the loop inserts the package name and a GETDATE() for the starttime in the log table with an INSERT query. The variable containing the package name is a parameter for this task. A very simple/basic log mechanisme. Adjust it to your needs or remove it if you have an other log mechanism.
INSERT INTO  FifoPackageQueueLog (Package, StartTime)
VALUES (?, GETDATE())


8) Execute Package Task

Add an expression on the packagename so that it gets replaced with the value of the variable. In the properties of the task set DelayValidation = True. This will prevent errors if your variable is empty.





















9) Log endtime
The second Execute SQL Task in the loop logs the enddate with an UPDATE query. The variable containing the package name is a parameter for this task. The start- and enddate will help you choose the optimal number of parallel tasks.
UPDATE  FifoPackageQueueLog
SET EndTime = GETDATE()
WHERE Package = ?
AND EndTime is null

10)  Get next package from queue
This is the exact same task/query as for getting the first package from the queue. If it can't find a package in the queue then it will fill the variable with an empty string and the For Loop will stop.

11) Multiple Execution lines
Repeat steps 4 to 10 an x times. Probably a couple more then you have processor cores in your server. Then start testing to find out the optimal number of parallel tasks.

12) Download example package
For SSIS 2012 I have added an example package for download. It contains 5 execution lines. Add more if you have more cores available. The example is provided for educational purposes only. This example package is not intended to be used in a production environment and has not been tested in a production environment. Test it thoroughly before using it.


Note: you will find a similar solution in the 10 Tips and Tricks for Better SSIS Performance presentation of David Peter Hansen and also in the SQL Rally Amsterdam presentation of Davide Mauri about Automating DWH Patterns Through Metadata. This example package is inspired by their solutions.

The process cannot acces the file 'ssisproject.ispac' because it is being used by another process.

$
0
0
Case
I want to run an SSIS 2012 package but I'm getting an error:
ispac file in use by other process






System.IO.IOException: The process cannot access the file 'c:\folder\ssisproject.ispac' because it is being used by another process.
     at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
     at System.IO.File.Delete(String path)
     at Microsoft.DataTransformationServices.Project.DataTransformationsProjectBuilder.IncrementalBuildThroughObj(IOutputWindow outputWindow)
     at Microsoft.DataTransformationServices.Project.DataTransformationsProjectBuilder.BuildIncremental(IOutputWindow outputWindow)


Solution
You have probably already restarted SSDT, but that didn't help. Open the Task Manager (Ctrl + Shift + Esc) and locate DtsDebugHost.exe under Processes and end that process (could be multiple times, end them all). Now try running the package again.
Task Manager, End SSIS Debug Host









This happens when SSDT/Visual Studio crashes during runtime. If you kill SSDT then the SSIS Debug Host will still be active locking the ISPAC file.


Checksum Transformation in BIML

$
0
0
Case
Last year we (colleague Marc Potters and me) created a custom Checksum Transformation for SSIS. We use it a lot to compare records from two sources. Instead of comparing a whole bunch of columns in a very large (unreadable and unmaintainable) expression we just compare the hash of both records.

Someone asked me if it was possible to add this custom transformation via BIML. The documentation and examples for custom transformations in BIML are a little limited and of course different for each one.

Solution
Make sure you install the Checksum Transformation. This BIML script uses version 1.3 of the Checksum Transformation in SSIS 2012 and BIDS Helper version 1.6.4. If you use an other version of Checksum or SSIS, then the ComponentClassId, ComponentTypeName and TypeConverter properties will probably have a different GUID or PublicKeyToken. By creating a package manually and viewing the source code you can find the correct values.












SELECT AddressLine1, AddressLine2, City FROM Address







0
Salt123

|




























Some browsers don't show capitals in the xml above, but you can download the BIML Script here.


Note 1: If you want to use a variable for the Salt, then you need to know the GUID of the variable. Create a variable in BIML, but with a GUID and use this GUID as Salt_Variable. See step 3 of this blog post.
Note 2: Don't change the names of the InputPath and OutputPath. The transformation is expecting these names.

Open BIML script without losing format and intellisense

$
0
0
Case
A couple of months ago I posted some tips about getting started with BIML. If you mix C# and BIML then you could loose formatting and intellisense. You can overcome this by right clicking the BIML Script and select Open With... Then choose the XML (Text) Editor. Now it opens with formatting and intellisense. It works, but it's still a little annoying...
Open in XML (Text) Editor















Solution
There is an easier solution: move the imports to the bottom of your script. Now close the script and open it the normal way.
Move imports to bottom





















Thanks to colleague @ralbouts
Viewing all 149 articles
Browse latest View live