2

I have a script component transformation in a data flow. In this script component I read a table from a object variable. The first record that passes through the script works fine. The variable reads correctly and loads into a list object perfectly.

The next record passes into the script something goes wrong.

Looking at the variable it reports a Record Count of 44, when it attempts to load into my list I get a rowcount = 0

Below is the script that loads the list

    List<PublicHoliday> PublicHolidays = new List<PublicHoliday>();


    OleDbDataAdapter A = new OleDbDataAdapter();
    DataTable dt = new DataTable();
    A.Fill(dt, Variables.LISTPublicHolidays);

    foreach (DataRow row in dt.Rows)
    {
        object[] array = row.ItemArray;
        var Public = new PublicHoliday()
        {
            DateKey = int.Parse(array[0].ToString()),
            FullDateAlternateKey = DateTime.Parse(array[1].ToString())
        };
        PublicHolidays.Add(Public);
    }

Am I missing something? Has anyone come across this issue before?

Hadi
  • 36,233
  • 13
  • 65
  • 124
Caz1224
  • 1,539
  • 1
  • 14
  • 37
  • What method, i.e. PreExecute, Input0_ProcessInputRow, PostExecute, is the code in? – userfl89 Mar 22 '19 at 14:16
  • @userfl89 what is mentioned in the answer is correct. The problem is that the object variable [contains a Recordset](https://learn.microsoft.com/en-us/dotnet/api/system.data.oledb.oledbdataadapter.fill?view=netframework-4.7.2#System_Data_OleDb_OleDbDataAdapter_Fill_System_Data_DataTable_System_Object_) when it is consumed using Fill() method it needs to be close and reopened or it will return empty result when executed multiple time. It is not related to the method where the code is executed – Yahfoufi Mar 22 '19 at 15:35
  • 1
    @Yahfoufi I see, that makes sense. Thanks – userfl89 Mar 22 '19 at 15:43
  • Check my answer update i added a new possible solution – Hadi Mar 22 '19 at 17:27

2 Answers2

2

Trying to figure out the issue

From the following OleDbDataAdapter.Fill Method (DataTable, Object) documentation:

CAUTION
When using ADO Recordset or Record objects in conjunction with .NET Framework applications, always call Close when you are finished. This ensures that the underlying connection to a data source is released in a timely manner, and also prevents possible access violations due to unmanaged ADO objects being reclaimed by garbage collection when existing references still exist.

Also referring to Filling a DataSet with an ADO Recordset or Record - documentation:

Note that the OleDbDataAdapter.Fill overload that takes a DataSet and an ADO object implicitly calls Close on the ADO object when the Fill operation is complete. You need to explicitly close the ADO Recordset or Record object after calling the OleDbDataAdapter.Fill overload that takes a DataTable.

Which implies that when calling Fill Method over a RecordSet you must close it before using it a second time or no rows will be returned.


Possible Workarounds

I don't know really how to close a Recordset from Object variable, but i will try to provide some possible workarounds:

(1) Saving to DataTable

In the link below they mentioned the following workaround:

  1. As soon as my Recordset @[User::FilePath] gets populated, I use a Script Task , and Fill it into a DataSet ds using OledbDataAdapter and DataTable.

  2. Then, in the same script task, I put the value of ds to a new variable of Object Type @[User::FilePathDataTable].

By doing this, the DataType of FilePathDataTable becomes System.Data.DataTable.

This datatable can easily be used any number of times inside the For-Each Loop.

I don't use DataAdapter.Fill() method inside ForEach Loop of ssis now. I just assign the Value of @[User::FilePathDataTable] to a new dataset, and use it for the iterations.

Reference

(2) Using Recordset Source

Instead of using Script Component to generate rows from Object variable try using RecordSet Source to do that.

(3) Cast variable as Recordset

I didn't tested this approach and I am not sure if it will works with object variable

It requires a reference to Microsoft ActiveX Data Objects.

List<PublicHoliday> PublicHolidays = new List<PublicHoliday>();

var rs = ((ADODB.Recordset)Variables.LISTPublicHolidays);
OleDbDataAdapter A = new OleDbDataAdapter();
DataTable dt = new DataTable();
A.Fill(dt, rs);

foreach (DataRow row in dt.Rows)
{
    object[] array = row.ItemArray;
    var Public = new PublicHoliday()
    {
        DateKey = int.Parse(array[0].ToString()),
        FullDateAlternateKey = DateTime.Parse(array[1].ToString())
    };
    PublicHolidays.Add(Public);
}
rs.Close();

Update 1

based on the comments below try removing rs.Close(); method from the first script and in the second script before executing Fill method use rs.MoverFirst() method to be able to retrieve information from recordset.

Third approach removed based on the following link:

Hadi
  • 36,233
  • 13
  • 65
  • 124
  • 1
    Hi Hadi, thanks for all the work you have been putting into this issue. Using your 3rd solution I am at least presented a workable error System.Runtime.InteropServices.COMException: 'Operation is not allowed when the object is closed.' – Caz1224 Mar 24 '19 at 22:07
  • @Caz1224 why not trying the first approach, i faced a similar issue from a while and i was able to solve it using this approach. regarding to the third approach i tried it with a recordset retrieved using a connection, i didn't really tried it using an object variable. – Hadi Mar 24 '19 at 22:13
  • @Caz1224 concerning the third approach, try removing the `rs.Close()` command from the first Script and in the second script add `Rs.MoveFirst()` before start reading from the object variable – Hadi Mar 24 '19 at 22:16
  • rs.close() removed, error has disappeared. Second read returns 0 rows - added rs.movefirst() before fill. – Caz1224 Mar 24 '19 at 22:59
  • @Caz1224 try the first approach – Hadi Mar 24 '19 at 23:01
  • @Caz1224 after checking the following link https://learn.microsoft.com/en-us/sql/ado/reference/ado-api/movefirst-movelast-movenext-and-moveprevious-methods-ado?view=sql-server-2017 the third approach will not work since the recordset cannot be consumed twice. You have to use the first approach – Hadi Mar 24 '19 at 23:05
  • 1
    I ended up going a different way. The ADO method was just causing too much pain. What I ended up doing was a TO JSON SQL query, saving that as an object, converting it to a string and then deserializing it as my list in the script component – Caz1224 Mar 24 '19 at 23:40
  • @Caz1224 well done. Hope that i was able to clarify the error cause. Good luck – Hadi Mar 24 '19 at 23:43
  • you did a heap of research and gave me a much better understanding of the ADO world, enough to let me know I didn't want to play in there! thanks for all your work. – Caz1224 Mar 24 '19 at 23:59
  • @Caz1224 feel free to write your own answer. And to upvote this one if you find it helpful :) – Hadi Mar 25 '19 at 04:56
  • @Caz1224 using `TO JSON SQL query, saving that as an object, converting it to a string and then deserializing it as my list in the script component` **it is not an answer for your question** it is a workaround. Your was asking for the **error cause** and it is answered in the first part of the answer above since it has provided all the information you need with many workarounds. This answer deserve to be upvoted and accepted. – Yahfoufi Mar 25 '19 at 09:15
  • 1
    Method #1 worked for me! Never would have thought it could be so simple. – Kelly Feb 25 '20 at 17:18
0

You should use ADO NET instead of Ole Db connection:

Dim dt As Data.DataTable
Dim ds As Data.DataSet = CType(Dts.Variables("Recordset").Value, DataSet)
dt = ds.Tables(0)

Reference: https://www.sqlshack.com/execute-sql-tasks-in-ssis-output-parameters-vs-result-sets

double-beep
  • 5,031
  • 17
  • 33
  • 41