Skip to main content

Building a Simple Package Generator with Biml

Why This Pattern Matters

A package generator turns rows in a metadata table into working SSIS packages. The metadata describes the packages: their names, their connections, parent-child relationships, and the constraint mode they should use. The Biml file describes the template that wraps each row. The compiler does the rest.

This walkthrough builds the smallest useful generator, with metadata for packages and connections only. The same pattern extends to data flows, mappings, and full extract-load patterns once the basics are in place.

The Metadata Schema

The minimum viable metadata for a generator needs three concepts:

  • A package definition, naming the package and storing its top level properties
  • A connection definition, storing the connection string and the connection type
  • A link from packages to the connections they use

Four tables cover this:

TablePurpose
pkg_definitionOne row per package to generate
conn_definitionOne row per connection
pkg_connectionMany to many link between packages and connections
conn_typeLookup of connection type names (OLEDB, ADONET, FILE, and so on)

A self reference on 'pkg_definition' captures parent-child relationships, useful when one package executes another.

The 'pkg_definition' table holds the columns needed to drive package level Biml attributes:

ColumnTypeNotes
PackageKeyIntIdentity, primary key
PackageNameNvarchar(200)Not null
ConstraintModeNvarchar(10)Default Parallel
ProtectionLevelNvarchar(50)Default EncryptSensitiveWithUserKey
ParentPackageKeyIntNullable, foreign key to PackageKey
GeneratePackageBitDefault 1

A new package definition is a single insert:

INSERT INTO meta.pkg_definition (PackageName)
VALUES ('LoadCustomerStage');

Splitting the Connection from the Generator

The Biml project starts with two files. The first holds only the connection back to the metadata database. Putting it in its own file makes it easy to edit when the metadata moves to a new server, without touching the generator.

<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<Connections>
<Connection
Name="FrameworkConfig"
ConnectionString="Data Source=DataServer;Initial Catalog=GeneratorMeta;Provider=SQLNCLI11.1;Integrated Security=SSPI;" />
</Connections>
</Biml>

The Generator File

The generator file declares the language as C#, imports the data namespaces, and reads the package list from the metadata database. The Biml inside the loop emits one Package element per row, with attributes bound to the row values:

<#@ template language="C#" hostspecific="true" tier="0" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Data" #>

<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<#
var configConn = (AstDbConnectionNode)RootNode.Connections["FrameworkConfig"];
#>
<Packages>
<#
var packagesToCreate = ExternalDataAccess.GetDataTable(
configConn.ConnectionString,
"SELECT PackageKey, PackageName, ConstraintMode, ProtectionLevel, GeneratePackage " +
"FROM meta.pkg_definition p " +
"WHERE GeneratePackage = 1 " +
"ORDER BY p.PackageName");

foreach (DataRow pkg in packagesToCreate.Rows) {
#>
<Package
Name="<#=pkg["PackageName"]#>"
ConstraintMode="<#=pkg["ConstraintMode"]#>"
ProtectionLevel="<#=pkg["ProtectionLevel"]#>">
<Tasks>
<Container Name="MainControlFlow" ConstraintMode="Linear">
<Variables></Variables>
<Tasks>
<Expression Name="Placeholder" Expression="1" />
</Tasks>
</Container>
</Tasks>
</Package>
<# } #>
</Packages>
</Biml>

The 'tier' attribute on the template directive tells the compiler the order in which Biml files run. Files with a lower tier run first, so this generator runs after the connection file (tier zero by default in this example, with the connection file being read on demand through 'RootNode.Connections').

Generating the Packages

In Visual Studio with BimlExpress, select both Biml files (shift-click), right click, and choose Check Biml For Errors. If the metadata query and the template both validate, right click again and choose Generate SSIS Packages. The compiler emits one .dtsx file per row in 'pkg_definition' where 'GeneratePackage' equals 1.

Each generated package contains a single sequence container with a placeholder Expression task. That placeholder is the spot where the data flow, the audit calls, and the framework specific tasks will go in subsequent iterations.

What This Sets Up

With the package list driven by metadata, adding a new package is an insert. The next pattern adds connections to each package: a 'pkg_connection' row points the package at the source and target connections, and the generator pulls those connection strings from 'conn_definition' inside the package element. After that, column-level mapping metadata feeds the data flow generation.

The result is a project where the entire ETL is described by a small set of relational rows, and the SSIS packages are a build artifact rather than something edited by hand.