Object Variable in script tasks - sql-server

In my package, I have an Execute Sql Task that sets the result set to a User variable. I then have a c# script task that needs to reference this User variable as a result set. I need the entire result set sent into my script tasks as the web service I am calling needs the entire result set in one shot.
This is the current code I am testing with. It isn't much as I am still trying to figure out where to go with it.
Any help with this is greatly appreciated
public void Main()
{
Variable resultSet = Dts.Variables["User::ZBatch_Order_Export_ResultSet"];
Dts.TaskResult = (int)ScriptResults.Success;
}
This is the update working code:
public void Main()
{
DataTable dt = new DataTable();
OleDbDataAdapter oleDa = new OleDbDataAdapter();
oleDa.Fill(dt, Dts.Variables["User::ZBatch_Order_Export_ResultSet"].Value);
foreach (DataRow row in dt.Rows)
{
Dts.Events
.FireError(0, "ZBatch - Script Task", row["orderDate"]
.ToString(), String.Empty, 0);
// Do some Webservice magic
}
Dts.TaskResult = (int)ScriptResults.Success;
}

So very close, to access the Value of a variable, you need to hit that property
public void Main()
{
Variable resultSet = Dts.Variables["User::ZBatch_Order_Export_ResultSet"].Value;
// do stuff here with resultSet and the webservice
Dts.TaskResult = (int)ScriptResults.Success;
}

Related

Script task in SSIS package is executing but not performing the action

I have the two SSIS packages which basically has two action like below
First it truncates the contents of the table and then it executes the script task like basically call an API and inserts the response in to the table
[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
public async void Main()
{
try
{
var sqlConn = new System.Data.SqlClient.SqlConnection();
ConnectionManager cm = Dts.Connections["SurplusMouse_ADONET"];
string serviceUrl = Dts.Variables["$Project::RM_ServiceUrl"].Value.ToString();
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(serviceUrl);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
string APIUrl = string.Format(serviceUrl + "/gonogo");
var response = await client.GetAsync(APIUrl);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
try
{
sqlConn = (System.Data.SqlClient.SqlConnection)cm.AcquireConnection(Dts.Transaction);
const string query = #"INSERT INTO [dbo].[RM_Approved_Room_State]
(APPROVED_ROOM_STATEID,SOURCE_ROOMID,DEST_ROOMID,ENTITY_TYPEID)
SELECT id, sourceRoomRefId, destinationRoomRefId,entityRefId
FROM OPENJSON(#json)
WITH (
id int,
sourceRoomRefId int,
destinationRoomRefId int,
entityRefId int
) j;";
using (var sqlCmd = new System.Data.SqlClient.SqlCommand(query, sqlConn))
{
sqlCmd.Parameters.Add("#json", SqlDbType.NVarChar, -1).Value = result;
await sqlCmd.ExecuteNonQueryAsync();
}
}
catch (Exception ex)
{
Dts.TaskResult = (int)ScriptResults.Failure;
}
finally
{
if (sqlConn != null)
cm.ReleaseConnection(sqlConn);
}
}
}
catch (Exception ex)
{
Dts.TaskResult = (int)ScriptResults.Failure;
}
}
#region ScriptResults declaration
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
#endregion
} }
Similar to the above package I have another one which does more likely does insert records into different table the response from a different endpoint. When I execute the packages locally/ execute them separately after deploying it in to the server it works fine. But when I add them in to the SQL Server Agent Job like below and run them on a schedule
The Jobs run successfully and dont show any errors but I can see only one table with data from one package but the other one truncates the records but I dont think the script task is getting executed / I dont see any records inserted. I dont think there are any issues with access because when I run them seperate manually the data are getting inserted, Just when it is running on a schedule it is not working as expected. Any idea what could be happening here.. Any help is greatly appreciated

.Net Using Transactions with Prepared Statements for SqlClient

I'm trying to implement Transactions for a script but I've run into a strange issue.
When I attempt to run a Prepared SQL Statement inside of a Transaction it is failing because it says that it needs a Transaction when the connection is assigned.
How does this work with Prepared Statements though because I intend to have multiple Transactions all using the same Prepared Statements.
My code is as follows
class dbTest {
public static SqlConnection db;
public static SqlCommand query;
static void Main(string[] args) {
db = connect();
prepare();
transaction01();
transaction02();
transaction03();
}
public static void prepare() {
query = new SqlCommand("select id from table where id = 1 for update", db);
query.Prepare();
}
public static void transaction01() {
SqlTransaction trans = db.BeginTransaction("Trn01");
SqlDataReader result = query.ExecuteReader();
while(result.Read()) { Console.WriteLine(result["id"]); }
result.Close();
trans.Commit();
}
public static void transaction02() {
SqlTransaction trans = db.BeginTransaction("Trn02");
SqlDataReader result = query.ExecuteReader();
while(result.Read()) { Console.WriteLine(result["id"]); }
result.Close();
trans.Commit();
}
public static void transaction03() {
SqlTransaction trans = db.BeginTransaction("Trn03");
SqlDataReader result = query.ExecuteReader();
while(result.Read()) { Console.WriteLine(result["id"]); }
result.Close();
trans.Commit();
}
}
How do I assign the Transaction to an existing Prepared Statement?
UPDATE
Changed the above code to better show the issue. The SQL is prepared once but I will be using it for multiple Transactions (or at least I want to)
UPDATE AGAIN
I have marked an answer below as the correct one because it looks like the best way to achieve this but for my needs in this very small example using query.Transaction got it working
public static void transaction01() {
SqlTransaction trans = db.BeginTransaction("Trn01");
query.Transaction = trans; // this line fixed it
SqlDataReader result = query.ExecuteReader();
while(result.Read()) { Console.WriteLine(result["id"]); }
result.Close();
trans.Commit();
}
When working with the SqlTransaction, you must set the SqlCommand.Transaction explicitly, even though enlisting in the current transaction is not optional in SQL Server.
select ... for update is not valid SQL Server syntax, instead use UPDLOCK to read a table and retain a restrictive lock for the duration of the transaction. EG
select id from table with (updlock) where id = 1
When I attempt to run a Prepared SQL Statement
It's rarely useful to use prepared statements with SQL Server. Query plan caching happens automatically even without it, and it really just reduces the size of the request on the network when you are executing a SqlCommand many times with differing parameters.
But a prepared SqlCommand is still bound to a single SqlConnection, which typically has a short lifetime, minimizing the potential benefits of preparing the SqlCommand.
You need to set the SqlCommand.Transaction to your transaction object.
it is not necessary with SQL Server to prepare the statement. Just keep executing.
Note also, as you can see in this post, that you must correctly dispose all DB objects.
Here is your code cleaned up:
class dbTest {
// DO NOT cache connection object
static void Main(string[] args) {
using(var db = connect())
using(var comm = GetCommand(db))
{
transaction01(comm);
transaction02(comm);
transaction03(comm);
}
}
public static SqlCommand GetCommand(SqlConnection conn) {
return new SqlCommand("select id from table with (updlock) where id = 1", conn);
}
public static void transaction01(SqlCommand comm) {
using(SqlTransaction trans = comm.Connection.BeginTransaction("Trn01"))
{
comm.Transaction = trans;
using(SqlDataReader result = query.ExecuteReader())
while(result.Read()) { Console.WriteLine(result["id"]); }
trans.Commit();
} // no need to close, using will sort that out
}
public static void transaction02(SqlCommand comm) {
using(SqlTransaction trans = comm.Connection.BeginTransaction("Trn02"))
{
comm.Transaction = trans;
using(SqlDataReader result = query.ExecuteReader())
while(result.Read()) { Console.WriteLine(result["id"]); }
trans.Commit();
} // no need to close, using will sort that out
}
public static void transaction03(SqlCommand comm) {
using(SqlTransaction trans = comm.Connection.BeginTransaction("Trn03"))
{
comm.Transaction = trans;
using(SqlDataReader result = query.ExecuteReader())
while(result.Read()) { Console.WriteLine(result["id"]); }
trans.Commit();
} // no need to close, using will sort that out
}
}

Can a array list of C# be used to populate SSIS object variable?

I have populated a list in C# script and assigned its value to SSIS object variable.
Then I used that object variable to execute some SQL query by looping through For each do enumerator.
I tried doing this by Foreach ado enumerator but getting error
X variable doesn't contain a valid data object.
Can anybody provide any inputs.
Youre using a list. Not a recordset and therefore you need to enumerate over a variable.
If you want to use ADO Recordset, you need to fill a datatable instead.
This shows you how to write to object with a variable list
This shows you how to write to object with recordset (using multiple values)
Like this:
1 .C# Script code - Write to Object with list using variable enumerator
public void Main()
{
// TODO: Add your code here
List<string> NewList = new List<string>();
NewList.Add("Ost");
NewList.Add("Hest");
Dts.Variables["User::NameList"].Value = NewList;
Dts.TaskResult = (int)ScriptResults.Success;
}
1. Variable settings in ssis
1. Foreach loop container settings
Use Foreach Variable Enumerator and use your object variable
Map your outcome to a variable(s)
1. Execute SQL Task test case
Write your SQL with variables
Map your variable to Parameter mapping
1. Result
2. C# Script code - Write to object with datatable using ADO enumerator
public void Main()
{
// TODO: Add your code here
DataTable dt = new DataTable();
dt.Columns.Add("FilmName",typeof(string));
dt.Columns.Add("ActorName",typeof(string));
dt.Rows.Add("Starwars", "Harrison ford");
dt.Rows.Add("Pulp fiction", "Samuel Jackson");
Dts.Variables["User::NameList"].Value = dt;
Dts.TaskResult = (int)ScriptResults.Success;
}
2. Variable settings in ssis
2. Foreach loop container settings
Use Foreach ADO Enumerator and your object as variable
Map your outcome to variable(s)
2. Execute sql task test case
Write your SQL with variables
Map your variable(s) to Parameter mapping
2. Result
Thanks #plaidDK
Second approch solved my problem
2.C# Script code - Write to object with datatable using ADO enumerator
Instead of list I have populated data table:
public DataTable ToDataTable<T>(List<T> items)
{
DataTable dataTable = new DataTable(typeof(T).Name);
//Get all the properties by using reflection
PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in Props)
{
//Setting column names as Property names
dataTable.Columns.Add(prop.Name);
}
foreach (T item in items)
{
var values = new object[Props.Length];
for (int i = 0; i < Props.Length; i++)
{
values[i] = Props[i].GetValue(item, null);
}
dataTable.Rows.Add(values);
}
return dataTable;
}
//Variable passed as below
Variables.vFailedTransactionNo = dt;
ANd then ado enumerator done rest of the job.
Thanks for help!

my combo box is duplicating the strings

help, combobox just keep adding items, i tried using removeallitems but after that i cant put anything on the first combobox
public class Function {
public void combofillsect(JComboBox section, String year){
Connection conn = null;
PreparedStatement pst = null;
ResultSet rs = null;
String query;
try{
query = "Select Section from asd where Year=?";
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","");
pst = conn.prepareStatement(query);
pst.setString(1, year);
rs = pst.executeQuery();
while(rs.next()){
section.addItem(rs.getString("Section"));
}
}catch(Exception e){
JOptionPane.showMessageDialog(null, e);
section.addItem(e.toString());
};
}
Function funct= new Function();
{funct.combofillsect(jComboBox1,String.valueOf(jComboBox2.getSelectedItem())); }
why cant I post image?
Are you programming in C# ? If it's the case then you can use the function Clear like that : yourComboBox.Items.Clear() to delete all the current items. I don't know if it will solve your problem but your technique of getting the data from your database seems weird to me, if you used a dataset you could have done dataset.Tables(0).Rows.Count() to get the number of entries and then set the exit condition of your loop like this -> counter < dataset.Tables(0).Rows.Count(), and set a counter++ at the end of your while (maybe that's why you say your combobox won't stop filling, but I don't know what do the next() function).
I don't know the C# code but there is my VB.NET function :
Public Function getAll() As DataSet
ConnectionDB()
Dim cmd As SqlClient.SqlCommand
cmd = New SqlClient.SqlCommand("SELECT * FROM table", Connect)//Connect is a System.Data.SqlClient.SqlConnection, or my connection string
Dim adapter As New Data.SqlClient.SqlDataAdapter
Dim dataset As New DataSet
adapter.SelectCommand = cmd
adapter.Fill(dataset)
adapter.Dispose()
cmd.Dispose()
Connect.Close()
Return dataset
End Function
I don't know if I helped you but I didn't really understood what your problem was and you didn't even mentioned the language you use ^^ Good luck
Edit : and if you can't post images, that's because you don't have yet 10 points of reputation, you can get informations about reputation here : https://stackoverflow.com/help/whats-reputation, but you still can post the link of a picture it will allow users to click on it

Is it possible to use `SqlDbType.Structured` to pass Table-Valued Parameters in NHibernate?

I want to pass a collection of ids to a stored procedure that will be mapped using NHibernate. This technique was introduced in Sql Server 2008 ( more info here => Table-Valued Parameters ). I just don't want to pass multiple ids within an nvarchar parameter and then chop its value on the SQL Server side.
My first, ad hoc, idea was to implement my own IType.
public class Sql2008Structured : IType {
private static readonly SqlType[] x = new[] { new SqlType(DbType.Object) };
public SqlType[] SqlTypes(NHibernate.Engine.IMapping mapping) {
return x;
}
public bool IsCollectionType {
get { return true; }
}
public int GetColumnSpan(NHibernate.Engine.IMapping mapping) {
return 1;
}
public void NullSafeSet(DbCommand st, object value, int index, NHibernate.Engine.ISessionImplementor session) {
var s = st as SqlCommand;
if (s != null) {
s.Parameters[index].SqlDbType = SqlDbType.Structured;
s.Parameters[index].TypeName = "IntTable";
s.Parameters[index].Value = value;
}
else {
throw new NotImplementedException();
}
}
#region IType Members...
#region ICacheAssembler Members...
}
No more methods are implemented; a throw new NotImplementedException(); is in all the rest. Next, I created a simple extension for IQuery.
public static class StructuredExtensions {
private static readonly Sql2008Structured structured = new Sql2008Structured();
public static IQuery SetStructured(this IQuery query, string name, DataTable dt) {
return query.SetParameter(name, dt, structured);
}
}
Typical usage for me is
DataTable dt = ...;
ISession s = ...;
var l = s.CreateSQLQuery("EXEC some_sp #id = :id, #par1 = :par1")
.SetStructured("id", dt)
.SetParameter("par1", ...)
.SetResultTransformer(Transformers.AliasToBean<SomeEntity>())
.List<SomeEntity>();
Ok, but what is an "IntTable"? It's the name of SQL type created to pass table value arguments.
CREATE TYPE IntTable AS TABLE
(
ID INT
);
And some_sp could be like
CREATE PROCEDURE some_sp
#id IntTable READONLY,
#par1 ...
AS
BEGIN
...
END
It only works with Sql Server 2008 of course and in this particular implementation with a single-column DataTable.
var dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
It's POC only, not a complete solution, but it works and might be useful when customized. If someone knows a better/shorter solution let us know.
A simpler solution than the accepted answer would be to use ADO.NET. NHibernate allows users to enlist IDbCommands into NHibernate transactions.
DataTable myIntsDataTable = new DataTable();
myIntsDataTable.Columns.Add("ID", typeof(int));
// ... Add rows to DataTable
ISession session = sessionFactory.GetSession();
using(ITransaction transaction = session.BeginTransaction())
{
IDbCommand command = new SqlCommand("StoredProcedureName");
command.Connection = session.Connection;
command.CommandType = CommandType.StoredProcedure;
var parameter = new SqlParameter();
parameter.ParameterName = "IntTable";
parameter.SqlDbType = SqlDbType.Structured;
parameter.Value = myIntsDataTable;
command.Parameters.Add(parameter);
session.Transaction.Enlist(command);
command.ExecuteNonQuery();
}
For my case, my stored procedure needs to be called in the middle of an open transaction.
If there is an open transaction, this code works because it is automatically reusing the existing transaction of the NHibernate session:
NHibernateSession.GetNamedQuery("SaveStoredProc")
.SetInt64("spData", 500)
.ExecuteUpdate();
However, for my new Stored Procedure, the parameter is not as simple as an Int64. It's a table-valued-parameter (User Defined Table Type)
My problem is that I cannot find the proper Set function.
I tried SetParameter("spData", tvpObj), but it's returning this error:
Could not determine a type for class: …
Anyways, after some trial and error, this approach below seems to work.
The Enlist() function is the key in this approach. It basically tells the SQLCommand to use the existing transaction. Without it, there will be an error saying
ExecuteNonQuery requires the command to have a transaction when the
connection assigned to the command is in a pending local transaction…
using (SqlCommand cmd = NHibernateSession.Connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = "MyStoredProc";
NHibernateSession.Transaction.Enlist(cmd); // Because there is a pending transaction
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("#wiData", SqlDbType.Structured) { Value = wiSnSqlList });
int affected = cmd.ExecuteNonQuery();
}
Since I am using the SqlParameter class with this approach, SqlDbType.Structured is available.
This is the function where wiSnList gets assigned:
private IEnumerable<SqlDataRecord> TransformWiSnListToSql(IList<SHWorkInstructionSnapshot> wiSnList)
{
if (wiSnList == null)
{
yield break;
}
var schema = new[]
{
new SqlMetaData("OriginalId", SqlDbType.BigInt), //0
new SqlMetaData("ReportId", SqlDbType.BigInt), //1
new SqlMetaData("Description", SqlDbType.DateTime), //2
};
SqlDataRecord row = new SqlDataRecord(schema);
foreach (var wi in wiSnList)
{
row.SetSqlInt64(0, wi.OriginalId);
row.SetSqlInt64(1, wi.ShiftHandoverReportId);
if (wi.Description == null)
{
row.SetDBNull(2);
}
else
{
row.SetSqlString(2, wi.Description);
}
yield return row;
}
}
You can pass collections of values without the hassle.
Example:
var ids = new[] {1, 2, 3};
var query = session.CreateQuery("from Foo where id in (:ids)");
query.SetParameterList("ids", ids);
NHibernate will create a parameter for each element.

Resources