Skip to main content

A Minimalistic Approach to Processing Packages with Biml

Why Keep the Metadata Small

Most metadata driven Biml examples cover staging and load. Processing packages, the ones that fire stored procedures and process SSAS databases, often get hand built. They do not have to. A tiny metadata model can describe both connections and ordered task lists, and a Biml script can build matching SSIS packages. The model below is deliberately small so it stays easy to maintain, but it scales to multiple projects.

The Metadata Model

Two tables and one view describe everything. Connections record where to run, packages record what to run and in which order:

USE [ProcessingControl]
GO
CREATE TABLE [dbo].[ProcMeta_Connections] (
[Connection] [nvarchar](50) NULL,
[Type] [nvarchar](50) NULL,
[Server] [nvarchar](50) NULL,
[Database] [nvarchar](50) NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[ProcMeta_Packages] (
[Package] [nvarchar](50) NULL,
[Tier] [int] NULL,
[StepName] [nvarchar](50) NULL,
[StepType] [nvarchar](50) NULL,
[Connection] [nvarchar](50) NULL,
[Task] [nvarchar](50) NULL,
[Parameter_1] [nvarchar](50) NULL,
[Parameter_2] [nvarchar](50) NULL
) ON [PRIMARY]
GO
CREATE VIEW [dbo].[vProcMeta_Packages] AS
SELECT c.Connection
, c.[Database]
, p.Package
, p.Tier
, p.StepName
, p.StepType
, ISNULL(p.Task, N'') AS Task
, ISNULL(p.Parameter_1, N'') AS Parameter_1
, ISNULL(p.Parameter_2, N'') AS Parameter_2
FROM dbo.ProcMeta_Connections AS c
INNER JOIN dbo.ProcMeta_Packages AS p ON c.Connection = p.Connection
GO
INSERT INTO [ProcMeta_Connections] SELECT 'CubeServer','OLAP','DwhServer','SalesCube'
INSERT INTO [ProcMeta_Connections] SELECT 'Warehouse','OLEDB','DwhServer','SalesWarehouse'
INSERT INTO [ProcMeta_Packages] SELECT 'OnlyOneProc',1,'Proc 1','SP','Warehouse','sp_RebuildSummary','100',''
INSERT INTO [ProcMeta_Packages] SELECT 'FullProcess',1,'Proc 1','SP','Warehouse','sp_RebuildSummary','100',''
INSERT INTO [ProcMeta_Packages] SELECT 'FullProcess',1,'Proc 2','SP','Warehouse','sp_LoadDim','''A'',20',''
INSERT INTO [ProcMeta_Packages] SELECT 'FullProcess',2,'Proc 3','SP','Warehouse','sp_LoadFact','5',''
INSERT INTO [ProcMeta_Packages] SELECT 'FullProcess',3,'Process Cube','OLAPDB','CubeServer','','',''

Connection rows declare a connection name, a type (OLEDB or OLAP), the server, and the database. Package rows declare the package name, the tier within the package, a step name, the step type (SP or OLAPDB), the connection to use, the task body (typically a stored procedure name), and up to two free form parameters. All rows sharing a package name go into the same package, all rows sharing a tier inside that package run in parallel, and tiers run in order.

Building Connections

The first tier loops the connection table and emits the right Biml node depending on type. OLEDB rows produce an OleDbConnection, OLAP rows produce an AnalysisServicesConnection:

<#@ template language="VB" tier="1" #>
<#@ import namespace="System.Data" #>
<# Dim controlConnString As String = "Provider=SQLNCLI11;Server=Localhost;Initial Catalog=ProcessingControl;Integrated Security=SSPI;" #>
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<Connections>
<OleDbConnection Name="Control" ConnectionString="<#= controlConnString #>" />
<#
For Each dr As DataRow In ExternalDataAccess.GetDataTable(controlConnString, "Select * from ProcMeta_Connections").Rows
If dr("Type") = "OLEDB" Then
#>
<OleDbConnection Name="<#= dr("Connection") #>" ConnectionString="Provider=SQLNCLI11;Server=<#= dr("Server") #>;Initial Catalog=<#= dr("Database") #>;Integrated Security=SSPI;" />
<#
ElseIf dr("Type") = "OLAP" Then
#>
<AnalysisServicesConnection Name="<#= dr("Connection") #>" Server="<#= dr("Server") #>" ConnectionString="Data Source=<#= dr("Server") #>;Initial Catalog=<#= dr("Database") #>;Provider=MSOLAP.6;Integrated Security=SSPI;Impersonation Level=Impersonate;" />
<#
End If
Next
#>
</Connections>
</Biml>

Adding a new connection type means adding one elseif branch.

Building Packages, Tiers, and Tasks

The second tier reads the control connection back out of the root node and queries the packages view to drive three nested loops: one per distinct package name, one per distinct tier inside that package, and one per task inside that tier. Containers run linearly to honor tier order, while tasks inside a container run in parallel:

<#@ template language="VB" tier="2" #>
<#@ import namespace="System.Data" #>
<#
Dim controlConn As AstDbConnectionNode = RootNode.Connections("Control")
Dim controlConnString As String = controlConn.ConnectionString
#>
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<Packages>
<# For Each pkg As DataRow In ExternalDataAccess.GetDataTable(controlConnString, "Select distinct Package from [vProcMeta_Packages]").Rows #>
<Package Name="<#= pkg("Package") #>" ConstraintMode="Linear">
<Tasks>
<# For Each tier As DataRow In ExternalDataAccess.GetDataTable(controlConnString, "Select distinct Tier from [vProcMeta_Packages] where Package = '" & pkg("Package") & "' order by Tier").Rows #>
<Container Name="Tier <#= tier(0) #>" ConstraintMode="Parallel">
<Tasks>
<# For Each task As DataRow In ExternalDataAccess.GetDataTable(controlConnString, "Select * from [vProcMeta_Packages] where Package = '" & pkg("Package") & "' and Tier = " & tier(0)).Rows #>
<# If task("StepType") = "SP" Then #>
<ExecuteSQL Name="<#= task("StepName") #>" ConnectionName="<#= task("Connection") #>">
<DirectInput>
EXEC <#= task("Task") #> <#= task("Parameter_1") #>
</DirectInput>
</ExecuteSQL>
<# ElseIf task("StepType") = "OLAPDB" Then #>
<AnalysisServicesProcessing Name="<#= task("StepName") #>" ConnectionName="<#= task("Connection") #>">
<ProcessingConfigurations>
<DatabaseProcessingConfiguration DatabaseId="<#= task("Database") #>" ProcessingOption="ProcessFull" />
</ProcessingConfigurations>
</AnalysisServicesProcessing>
<# End If #>
<# Next #>
</Tasks>
</Container>
<# Next #>
</Tasks>
</Package>
<# Next #>
</Packages>
</Biml>

The if elseif block is the extension point. Adding dimension processing, partition processing, backups, or any other task type means adding one more branch and (if the task uses a new connection type) one more entry in the connections tier.

The result is a single Biml project that produces a complete processing layer for any number of orchestration packages, all driven by a few rows of metadata.