Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

SSIS Data Flow Error Handling With Description and Column Name

DZone's Guide to

SSIS Data Flow Error Handling With Description and Column Name

Without SQL 2016's modified API, it's really difficult to find the error column name using just a script task and script component. Here's a simple solution.

· Database Zone ·
Free Resource

Running out of memory? Learn how Redis Enterprise enables large dataset analysis with the highest throughput and lowest latency while reducing costs over 75%! 

Finding an error description is a simple task involving one line of code — but finding the error column name is not so simple. Microsoft made it easy with SQL 2016's modified API. But with prior versions, it's a really difficult job to find the error column name using just a script task and script component.

This article provides a simple step-by-step solution to find the error description and error column name.

Scenario

We are trying to insert records from a flat file to our SQL table. The flat file has two columns: roll and name. Sometimes, the name column contains huge text and isn't supported by our table object (the size of the name column the in table object is Varchar(50)). This generates an error. We are trying to display the error description and the column name that generated the error.

Control/Data Flow Details

Image title

Control Flow: Get Lineage ID Task

This script task is used to grade the lineage ID at runtime and save it as a text file.

  1. Create a text file: C:\temp\write.txt.

  2. Create two package-level variables named User::execsObj and User::lineageIds as the object types. Pass them as the Read-Write variables of the script task.

  3. In the script task, add an assembly named Microsoft.SqlServer.DTSPipelineWrap: 

    using System;
    using System.Data;
    using Microsoft.SqlServer.Dts.Runtime;
    using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
    using System.Windows.Forms;
    using System.Collections.Generic;
    using System.IO;

    Script task code in C#:

    {
     // Grab the executables so we have to something to iterate over, and initialize our lineageIDs list
    // Why the executables?  Well, SSIS won't let us store a reference to the Package itself...
    Dts.Variables["User::execsObj"].Value =(Package)Dts.Variables["User::execsObj"].Parent).Executables;
    
    Dts.Variables["User::lineageIds"].Value = new Dictionary<int, string>();
    lineageIds = (Dictionary<int, string>)Dts.Variables["User::lineageIds"].Value;
    Executables execs = (Executables)Dts.Variables["User::execsObj"].Value;
    
    ReadExecutables(execs);
    
    // Just proof of concept to see the results before you dedicate your time to the solution
    // Delete this code in your actual implementation
    using (StreamWriter writetext = new StreamWriter(@"C:\temp\write.txt", true))
            {
                    foreach (var kvp in lineageIds)
                    writetext.WriteLine(kvp.Key + " : " + kvp.Value);
            }
                Dts.TaskResult = (int)ScriptResults.Success;
    }
    
            
    
    private void ReadExecutables(Executables executables)
    {
      foreach (Executable pkgExecutable in executables)
        {
         if (object.ReferenceEquals(pkgExecutable.GetType(),            
                     typeof(Microsoft.SqlServer.Dts.Runtime.TaskHost)))
                    {
                        TaskHost pkgExecTaskHost = (TaskHost)pkgExecutable;
                        if (pkgExecTaskHost.CreationName.StartsWith("SSIS.Pipeline"))
                        {
                            ProcessDataFlowTask(pkgExecTaskHost);
                        }
                    }
         else if (object.ReferenceEquals(pkgExecutable.GetType(), 
                     typeof(Microsoft.SqlServer.Dts.Runtime.ForEachLoop)))
                    {
                        // Recurse into FELCs
                        ReadExecutables(((ForEachLoop)pkgExecutable).Executables);
                    }
          }
     }
    
    private void ProcessDataFlowTask(TaskHost currentDataFlowTask)
    {
       MainPipe currentDataFlow = (MainPipe)currentDataFlowTask.InnerObject;
    
       foreach (IDTSComponentMetaData100 currentComponent in 
                      currentDataFlow.ComponentMetaDataCollection)
                {
                  // Get the inputs in the component.
                  foreach (IDTSInput100 currentInput in currentComponent.InputCollection)
                      foreach (IDTSInputColumn100 currentInputColumn in 
                              currentInput.InputColumnCollection)
                            lineageIds.Add(currentInputColumn.ID, currentInputColumn.Name);
    
                   // Get the outputs in the component.
                   foreach (IDTSOutput100 currentOutput in currentComponent.OutputCollection)
                        foreach (IDTSOutputColumn100 currentoutputColumn in 
                                    currentOutput.OutputColumnCollection)
                            lineageIds.Add(currentoutputColumn.ID, currentoutputColumn.Name);
                }
    }

Adding a Script Component to the Data Flow Task

Now, add a script component to the data flow task.

  1. Pass the variable named User::lineageIds as the Read variable.

  2. Script the component input columns as ErrorCode and ErrorColumn.

  3. Script the component output column as ErrorDescription and ErrorColumnDescription with a DT_STR data type.

    using System;
    using System.Data;
    using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
    using Microsoft.SqlServer.Dts.Runtime.Wrapper;
    using System.Collections.Generic;
    Script task code in C#:
    public override void Input0_ProcessInputRow(Input0Buffer Row)
        {
            Dictionary<int, string> lineageIds = (Dictionary<int, string>)Variables.lineageIds;
    
            int? colNum = Row.ErrorColumn;
            if (colNum.HasValue && (lineageIds != null))
            {
                if (lineageIds.ContainsKey(colNum.Value))
                    Row.ErrorColumnDescription = lineageIds[colNum.Value];
    
                else
                    Row.ErrorColumnDescription = "Row error";
            }
            Row.ErrorDescription = this.ComponentMetaData.GetErrorDescription(Row.ErrorCode);
         }

I hope you found this article informative!

Running out of memory? Never run out of memory with Redis Enterprise databaseStart your free trial today.

Topics:
database ,tutorial ,data flow ,api ,error handling ,ssis ,sql server

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}