DTS Example: Running Concurrent Operations in Visual Basic

DTS Programming

DTS Programming

DTS Example: Running Concurrent Operations in Visual Basic

Custom tasks implemented in Microsoft® Visual Basic® must run on the package main thread because Visual Basic does not support free threading. Therefore, tasks implemented in Visual Basic and run in a Data Transformation Services (DTS) package in DTS Designer run sequentially, even when the package has no precedence constraints.

However, in a DTS package application, one task running on the main thread can run concurrently with others on worker threads. Thus, such a task could be implemented in Visual Basic.

DTS Designer also does not allow a task to display a modeless form or dialog box. However, in a DTS application modeless forms can be displayed. A modeless form is used in this example.

Concurrent Display

The following example code implements a DTS custom task that continuously displays the value of a global variable while other tasks are running. The custom task closes the display when the value of another global variable changes to TRUE.

This Visual Basic project consists of a custom task class and a runtime display form.

Custom Task Class

In the custom task class, called ShowGlobal:

  • Properties GVMonitor and GVFinish specify the names of the global variable to be displayed and to the global variable that indicates completion, respectively. There is no property page user interface, as the application sets the properties directly.

  • The global variable display is updated continuously. The task raises the OnProgress and OnQueryCancel events every 3 seconds.

  • A log file string and a task record are written when task execution completes.

  • The DTS properties provider is explicitly invoked. The PersistPropertyBag interface is implemented.
Implementing the ShowGlobal Class

This is the Visual Basic code for the ShowGlobal class:

Implements DTS.CustomTask
Implements DTS.PersistPropertyBag

Const INVAL_PROP = "Invalid property value."

Private mstrDescription     As String   'Task/ShowAGlobal.Description property
Private mstrTaskName        As String   'Task/ShowAGlobal.Name property
Private mstrGVMonitorName   As String   'ShowAGlobal.GVMonitor property
Private mstrGVFinishName    As String   'ShowAGlobal.GVFinish property
Private frmShowGV           As frmFinalGlobal
Private objTask             As DTS.Task

Private Sub CustomTask_Execute(ByVal pPackage As Object, _
            ByVal pPackageEvents As Object, ByVal pPackageLog As Object, _
            pTaskResult As DTS.DTSTaskExecResult)
'Display value of global variable until another global indicates display finished.
    Dim objPackage      As DTS.Package2
    Dim objMonitor      As DTS.GlobalVariable
    Dim objFinished     As DTS.GlobalVariable
    Dim blnCancel       As Boolean
    Dim datCurrTime     As Date
    Dim datStartTime    As Date
    
    'Save reference to package, release parameter reference.
    Set objPackage = pPackage
    Set pPackage = Nothing
    pTaskResult = DTSTaskExecResult_Success
    
    'Initialize times for event generation.
    datStartTime = Now
    datCurrTime = Now
    
    'Get reference to global variables, exit if already finished.
    Set objMonitor = objPackage.GlobalVariables(mstrGVMonitorName)
    Set objFinished = objPackage.GlobalVariables(mstrGVFinishName)
    If objFinished.Value = True Then Exit Sub
    
    'Display form and use global variable name as title.
    Set frmShowGV = New frmFinalGlobal
    frmShowGV.MonitorName = mstrGVMonitorName
    frmShowGV.Show vbModeless
    
    'Refresh display until finished GV indicates done, user closes form, or app indicates Cancel.
    Do Until objFinished.Value Or frmShowGV.Unloaded
        frmShowGV.MonitorValue = objMonitor.Value
        
        'Every 3 sec, raise OnQueryCancel and OnProgress.
        If DateDiff("s", datCurrTime, Now) >= 3 Then
            datCurrTime = Now
            
            'Make sure package events object is valid.
            If Not pPackageEvents Is Nothing Then
            
                'Raise On Progress, OnQueryCancel, exit if response says to cancel.
                pPackageEvents.OnProgress Me.Description, "3 second notification", _
                        0, DateDiff("s", datStartTime, Now), 0
                pPackageEvents.OnQueryCancel Me.Description, blnCancel
                If blnCancel Then Exit Do
            End If
        End If
        
        DoEvents
    Loop
    
    'Write elapsed time and GV value to log, if log object valid.
    If Not pPackageLog Is Nothing Then
        pPackageLog.WriteStringToLog Me.Description & ": " & _
                    objMonitor.Name & " = " & objMonitor.Value
        pPackageLog.WriteTaskRecord 0, _
                    Me.Description & " elapsed time: " & _
                    (DateDiff("s", datStartTime, Now)) & " sec."
    End If
        
    'Close and release form.
    Unload frmShowGV
    Set frmShowGV = Nothing
        
End Sub

Private Property Get CustomTask_Properties() As DTS.Properties
'Use DTS properties provider to generate collection.
    Dim oPropsProvider As New DTS.PropertiesProvider
    
    Set CustomTask_Properties = oPropsProvider.GetPropertiesForObject(Me)
    Set oPropsProvider = Nothing

End Property

Private Property Let CustomTask_Description(ByVal strNewDescr As String)
'Implements Task.Description.
    mstrDescription = strNewDescr
End Property

Private Property Get CustomTask_Description() As String
'Implements Task.Description.
    CustomTask_Description = mstrDescription
End Property

Private Property Let CustomTask_Name(ByVal strNewName As String)
'Implements Task.Name.
    mstrTaskName = strNewName
End Property

Private Property Get CustomTask_Name() As String
'Implements Task.Name.
    CustomTask_Name = mstrTaskName
End Property

Private Sub PersistPropertyBag_Save(ByVal propBag As DTS.PropertyBag)
'Save property values in property bag.

    'On Error Resume Next
    propBag.Write "Name", mstrTaskName
    propBag.Write "Description", mstrDescription
    propBag.Write "GVMonitor", mstrGVMonitorName
    propBag.Write "GVFinish", mstrGVFinishName
    
End Sub

Private Sub PersistPropertyBag_Load(ByVal propBag As DTS.PropertyBag)
'Load property values from property bag.

    'On Error Resume Next.
    mstrTaskName = propBag.Read("Name")
    mstrDescription = propBag.Read("Description")
    mstrGVMonitorName = propBag.Read("GVMonitor")
    mstrGVFinishName = propBag.Read("GVFinish")
    
End Sub

Public Property Get GVMonitor() As String
'Name of global variable to monitor.
     GVMonitor = mstrGVMonitorName
End Property

Public Property Let GVMonitor(ByVal strNewName As String)
'Name of global variable to monitor, verify non-empty.
    
    If Len(strNewName) > 0 Then
        mstrGVMonitorName = strNewName
    Else
        Err.Raise 1001 + vbObjectError, Me.Name, INVAL_PROP
    End If
    
End Property

Public Property Get GVFinish() As String
'Name of global variable to indicate finish.
     GVFinish = mstrGVFinishName
End Property

Public Property Let GVFinish(ByVal strNewName As String)
'Name of global variable to indicate finish, verify non-empty.
    
    If Len(strNewName) > 0 Then
        mstrGVFinishName = strNewName
    Else
        Err.Raise 1001 + vbObjectError, Me.Name, INVAL_PROP
    End If
    
End Property

Public Property Get Name() As String
'Implements FinalGlobal.Name.
    Name = mstrTaskName
End Property

Public Property Let Name(ByVal strNewName As String)
'Implements FinalGlobal.Name
    mstrTaskName = strNewName
End Property

Public Property Get Description() As String
'Implements FinalGlobal.Description
    Description = mstrDescription
End Property

Public Property Let Description(ByVal strNewDescr As String)
'Implements FinalGlobal.Description
    mstrDescription = strNewDescr
End Property

To build this DTS custom task

  1. In the Visual Basic development environment, create a new ActiveX DLL project.

  2. On the Project menu, click References, and under Available References, select the check box for Microsoft DTSPackage Object Library. Then, on the Project menu, click Properties and in the Project name box, change the project name from Project1 to something meaningful, like DTSConcurrentSample.

  3. Copy the code for the ShowGlobal class in the preceding code example to the class module in the Visual Basic project. Change the name of the class module from Class1 to ShowGlobal. If you use a different name, you need to change the references to ShowGlobal in the code to that name.

  4. Add the frmFinalGlobal form.

    For more information about this form, see DTS Example: Including a User Interface in Visual Basic.

  5. On the File menu, click Make DTSConcurrentSample.dll to build the component.

    Do not register the task in DTS Designer.

DTS Package Application

This DTS application uses the ShowGlobal custom task to display the number of rows copied, via the Rows Copied global variable, while another step copies rows from a table in one database to a table in another. The copy step closes the display by setting the Copy Complete global variable to TRUE.

The copy step uses a DataPumpTask2 object with a DataPumpTransformScript transformation to copy certain columns from the Products table in the Microsoft SQL Server™ Northwind database to a table named NorthwindProducts in a database named DTS_UE. DTS lookups are used to replace the CategoryID field with the CategoryName from the Northwind Categories table, and to replace the SupplierID field with the CompanyName from the Northwind Suppliers table.

Creating the DTS Package Application

This is the definition of the NorthwindProducts table in DTS_UE:

CREATE TABLE [DTS_UE].[dbo].[NorthwindProducts] (
   [ProductName] [nvarchar] (40) NULL ,
   [CategoryName] [nvarchar] (25) NULL ,
   [CompanyName] [nvarchar] (40) NULL ) 

This is the Visual Basic code for the application:

Public Sub Main()
'Copy Northwind..Products names, categories, suppliers to DTS_UE..NorthwindProducts.
    Dim objPackage    As DTS.Package2
    Dim objConnect    As DTS.Connection2
    Dim objStep       As DTS.Step2
    Dim objTask       As DTS.Task
    Dim objPumpTask   As DTS.DataPumpTask2
    Dim objCustTask   As DTSConcurrentSample.ShowGlobal
    Dim objTransform  As DTS.Transformation2
    Dim objLookUp     As DTS.Lookup
    Dim objTranScript As DTSPump.DTSTransformScriptProperties2
    Dim sVBS          As String       'VBScript text

    Set objPackage = New DTS.Package
    objPackage.FailOnError = True
    objPackage.LogFileName = "C:\Temp\TestConcurrent.Log"
    
    'Establish connections to data source and destination.
    Set objConnect = objPackage.Connections.New("SQLOLEDB.1")
    With objConnect
        .ID = 1
        .DataSource = "(local)"
        .UseTrustedConnection = True
    End With
    objPackage.Connections.Add objConnect
    Set objConnect = objPackage.Connections.New("SQLOLEDB.1")
    With objConnect
        .ID = 2
        .DataSource = "(local)"
        .UseTrustedConnection = True
    End With
    objPackage.Connections.Add objConnect

    'Create copy step and task, link step to task.
    Set objStep = objPackage.Steps.New
    objStep.Name = "NorthwindProductsStep"
    Set objTask = objPackage.Tasks.New("DTSDataPumpTask")
    Set objPumpTask = objTask.CustomTask
    objPumpTask.Name = "NorthwindProductsTask"
    objStep.TaskName = objPumpTask.Name
    objStep.ExecuteInMainThread = False
    objPackage.Steps.Add objStep
    
    'Link copy task to connections.
    With objPumpTask
        .SourceConnectionID = 1
        .SourceSQLStatement = _
            "SELECT ProductName, CategoryID, SupplierID " & _
            "FROM Northwind..Products"
        .DestinationConnectionID = 2
        .DestinationObjectName = "[DTS_UE].[dbo].[NorthwindProducts]"
        .UseFastLoad = False
        .MaximumErrorCount = 99
    End With
    
    'Create lookups for supplier and category.
    Set objLookUp = objPumpTask.Lookups.New("CategoryLU")
    With objLookUp
        .ConnectionID = 1
        .Query = "SELECT CategoryName FROM Northwind..Categories " & _
                "WHERE CategoryID = ? "
        .MaxCacheRows = 0
    End With
    objPumpTask.Lookups.Add objLookUp
    Set objLookUp = objPumpTask.Lookups.New("SupplierLU")
    With objLookUp
        .ConnectionID = 1
        .Query = "SELECT CompanyName FROM Northwind..Suppliers " & _
                "WHERE SupplierID = ? "
        .MaxCacheRows = 0
    End With
    objPumpTask.Lookups.Add objLookUp
    
    'Create and initialize rowcount and completion global variables.
    objPackage.GlobalVariables.AddGlobalVariable "Copy Complete", False
    objPackage.GlobalVariables.AddGlobalVariable "Rows Copied", 0
    objPackage.ExplicitGlobalVariables = True
    
    'Create transform to copy row, signal completion.
    Set objTransform = objPumpTask.Transformations. _
            New("DTSPump.DataPumpTransformScript")
    With objTransform
        .Name = "CopyNorthwindProducts"
        .TransformPhases = DTSTransformPhase_Transform + _
                        DTSTransformPhase_OnPumpComplete
        Set objTranScript = .TransformServer
    End With
    With objTranScript
        .FunctionEntry = "CopyColumns"
        .PumpCompleteFunctionEntry = "PumpComplete"
        .Language = "VBScript"
        sVBS = "Option Explicit" & vbCrLf
        sVBS = sVBS & "Function CopyColumns()" & vbCrLf
        sVBS = sVBS & "   DTSDestination(""ProductName"") = DTSSource(""ProductName"") " & vbCrLf
        sVBS = sVBS & "   DTSDestination(""CategoryName"") = DTSLookups(""CategoryLU"").Execute(DTSSource(""CategoryID"")) " & vbCrLf
        sVBS = sVBS & "   DTSDestination(""CompanyName"") = DTSLookups(""SupplierLU"").Execute(DTSSource(""SupplierID"").Value) " & vbCrLf
        sVBS = sVBS & "   DTSGlobalVariables(""Rows Copied"") = CLng(DTSTransformPhaseInfo.CurrentSourceRow)" & vbCrLf
        sVBS = sVBS & "   CopyColumns = DTSTransformStat_OK" & vbCrLf
        sVBS = sVBS & "End Function" & vbCrLf
        
        sVBS = sVBS & "Function PumpComplete()" & vbCrLf
        sVBS = sVBS & "   DTSGlobalVariables(""Copy Complete"") = True" & vbCrLf
        sVBS = sVBS & "   PumpComplete = DTSTransformStat_OK" & vbCrLf
        sVBS = sVBS & "End Function" & vbCrLf
        
        .Text = sVBS
    End With
    objPumpTask.Transformations.Add objTransform
    objPackage.Tasks.Add objTask
    
    'Create monitor step and task, link step to task.
    Set objStep = objPackage.Steps.New
    objStep.Name = "GVMonitorStep"
    Set objTask = objPackage.Tasks.New("DTSConcurrentSample.ShowGlobal")
    objTask.Name = "GVMonitorTask"
    objStep.TaskName = objTask.Name
    Set objCustTask = objTask.CustomTask
    objCustTask.GVMonitor = "Rows Copied"
    objCustTask.GVFinish = "Copy Complete"
    objStep.ExecuteInMainThread = True
    objPackage.Steps.Add objStep
    
    'Link monitor task to package, run package.
    objPackage.Tasks.Add objTask
    objPackage.Execute
   
End Sub

Important  This sample application is intentionally implemented to run slowly so the display will be more convenient to view. It uses the same connection for the data source and both lookups, and uses 0 for the MaxCacheRows property for both lookups. In an actual application that copies and transforms databases, you should use a separate connection for lookups, or for each lookup. You should also use a nonzero value for MaxCacheRows. The default of 100 is recommended for the initial choice.

To build this DTS application

  1. Build the DTSConcurrentSample.ShowGlobal custom task, as described in the preceding example.

  2. Create a database named DTS_UE containing a table named NorthwindProducts, using the definition given above.

  3. Create a Standard EXE project in the Visual Basic development environment.

  4. On the Project menu, click References, and under Available References, select the check boxes for DTSConcurrentSample, Microsoft DTSDataPump Scripting Object Library and Microsoft DTSPackage Object Library.

  5. Add a standard module to the project, and then copy the code for the application in the preceding code example into the module. Remove the form Form1 from the project.

  6. On the Run menu, click Start.

    Observe the displayed rowcount.

See Also

DataPumpTask2 Object

DataPumpTransformScript Object

DTS Example: Including a User Interface in Visual Basic

MaxCacheRows Property

OnProgress Event

OnQueryCancel Event

PersistPropertyBag Object