We've been given a "stored procedure" from our RPG folks that returns six data tables. Attempting to call it from .NET (C#, 3.5) using the iSeries Provider for .NET (tried using both V5R4 and V6R1), we are seeing different results based on how we call the stored proc. Here's way that we'd prefer to do it:
using (var dbConnection = new iDB2Connection("connectionString"))
{
dbConnection.Open();
using(var cmd = dbConnection.CreateCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "StoredProcName";
cmd.Parameters.Add(new iDB2Parameter("InParm1",
iDB2DbType.Varchar).Value = thing;
var ds = new DataSet();
var da = new iDB2DataAdapter(cmd);
da.Fill(ds);
}
}
Doing it this way, we get FIVE tables back in the result set. However, if we do this:
cmd.CommandType = CommandType.Text;
cmd.CommandText = "CALL StoredProcName('" + thing + "')";
We get back the expected SIX tables.
I realize that there aren't many of us sorry .NET-to-DB2 folks out here, but I'm hoping someone has seen this before.
TIA.
Look into the LibraryList (and maybe the Naming) property of your connection string. When you use CommandType.StoredProcedure it could be executing the stored procedure right from the SQL database library. When you use CommandType.Text it searches the library list to find the stored procedure. You end up running different versions of the stored procedure from different libraries which gives you different results.
Related
After reading an interesting article online : Calling DB2 stored procedures from .NET applications
I'd like to share an issue recently encountered with a derived code :
DateTime transa_date = DateTime.ParseExact(trandate, "yyyy-MM-dd",
CultureInfo.InvariantCulture);
DB2Connection conn = new DB2Connection(MyDb2ConnectionString);
conn.Open();
try
{
// MyDb2Connection.Open();
// conn.Open();
// assume a DB2Connection conn
DB2Transaction trans = conn.BeginTransaction();
cmd = conn.CreateCommand();
procName = "MYTBLSCHEMA.TEST";
procCall = "CALL MYTBLSCHEMA.TEST(#NAME, #ADDRESS_LINE, #REGNUM, #TRANSA)";
cmd.Transaction = trans;
cmd.CommandType = System.Data.CommandType.Text;
cmd.CommandText = procCall;
// Register input-output and output parameters for the DB2Command
cmd.Parameters.Add( new DB2Parameter("#NAME", name)); #of string type
cmd.Parameters.Add( new DB2Parameter("#ADDRESS_LINE", adr)); #of string type
cmd.Parameters.Add( new DB2Parameter("#REGNUM", reg)); #of string type
cmd.Parameters.Add( new DB2Parameter("#TRANSA", transa_date)); #of date type (in DB2 table)
// Call the stored procedure
Console.WriteLine(" Call stored procedure named " + procName);
cmd.ExecuteNonQuery();
}
The above code neither generates an exception at cmd.ExecuteNonQuery() nor inserts the (expected) row into the table.
Hence, a Hope to understand through this post the rationale underlying such phenomenon.
Thanks.
N.B: Executing (manually)
CALL MYTBLSCHEMA.TEST('test', 'test_address_', 'test_num', 2021-01-01)
from the IDE does work (e.g. insert the row into the table).
DB2 version: 11.5.6.0.00000.008
I'd either remove this line:
DB2Transaction trans = conn.BeginTransaction();
Or I'd add this line at the end of the try:
trans.Commit();
As to which you'd choose; as it's a single stored procedure, unless there's some internal overriding concern within the sproc that makes sense to have a transaction to be started outside it cover it, I'd remove it. If you have, or plan to have multiple operations that must either all-succeed or all-fail, then I'd keep it/commit it..
[Edit - an explanation of the answer is at the foot of the question because the subtlety is not totally obvious from the accepted answer.]
Original question:
I am looking for a quick 'yes this approach should be possible' answer before I commit further effort.
Scenario:
I have a simple SQL Server stored procedure that runs a query. I also want to (1) return a varchar(100) parameter declared as OUTPUT, and (2) use the #ReturnValue to return a further (integer) value.
I call this procedure from C# using the SqlDatAdapter.DataSet.Fill() approach.
Back in the C# code, I can access the procedure parameters but I cannot get the values of either #ReturnValue or the other OUTPUT param.
What am I expecting ?
The SqlDataAdapter fill process to grab the data from the select. This works and I can access this data in C#.
The values of the SQL Server stored procedure params marked as OUTPUT, and the automatic #ReturnValue param, to be available in C#.
What do I see ?
Data is returned from the query as expected.
The SQL command parameters are present in C#, but their .Value properties are blank.
What research have I done ?
A lot of googling for hits containing SqlDataAdapter and #returnvalue. No significant positive results.
Perused the old SO questions related to SqlDataAdapter - nothing seems to overlap my question.
Posted this question.
I need to ask the SO community if this is even possible. So - is it possible to collect OUTPUT parameter values from a stored procedure that was used with SqlDatAdapter.DataSet.Fill() ?
If the answer is yes and there are no simple examples out there then I'll cut down my code to a representative case and post it here for (hopefully) community debugging.
EDIT
Thanks to #NDJ and the sample code supplied I was able to confirm that SqlDatAdapter.DataSet.Fill() DOES NOT interfere with stored procedure return values. I was then able to go on to discover that my issue was caused by an immediate call to SqlDatAdapter.DataSet.FillScheam(). This delivers the metadata for the data table into a handy matrix that we can access from C#, which is part of my use case.
However, FillSchema() clears the stored procedure parameter values. In my code I was trying to gather the stored procedure output parameter values after the call to FillSchema, which was the cause of what I witnessed. I could not find any documentation on this point.
If your code looks like this, it will work.
da.Fill(ds);
var returnVal = returnParam.Value; // value as expected
da.FillSchema(ds, SchemaType.Source);
If your code looks like this it will not work
da.Fill(ds);
da.FillSchema(ds, SchemaType.Source);
var returnVal = returnParam.Value; // value reset by prev line !!!!
yes you can.
Create PROCEDURE TestSP
#test varchar(max) output
AS
BEGIN
select #test = 'abc'
select top 10 * from MyTable
return 4
END
using (SqlConnection connection = new SqlConnection(conString))
{
connection.Open();
SqlCommand cmd = new SqlCommand("TestSP", connection);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter param = new SqlParameter("#test", SqlDbType.VarChar, -1);
param.Direction = ParameterDirection.Output;
var returnParam = cmd.Parameters.Add("#Return", SqlDbType.Int);
returnParam.Direction = ParameterDirection.ReturnValue;
cmd.Parameters.Add(param);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
var returnVal = returnParam.Value; //4
var output = param.Value; // abc
var data = ds.Tables[0].Rows[0][0]; //data from my table
}
When calling a stored procedure using either ExecuteResultSet or ExecuteReader
using (DB2Connection conn = new DB2Connection(connstr))
{
conn.Open();
DB2Command cmd = conn.CreateCommand();
cmd.Transaction = conn.BeginTransaction();
DB2Parameter db2param = new DB2Parameter("#ENTERPRISE_ID_PR091", DB2Type.Char, 15);
db2param.Direction = ParameterDirection.InputOutput;
db2param.Value = enterpriseID.ToCharArray();
cmd.Parameters.Add(db2param);
//... many parameters
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "XXISCHMA.XXIPW09D";
DB2ResultSet dr = cmd.ExecuteResultSet(DB2ResultSetOptions.Scrollable);
}
In Web API the Exceptionis thrown:
SQL0035N The file "C:\Users\documents\visual studio 2013\Projects\App\Web\msg\en_US\db2nmp.xml" cannot be opened
In other Applications an Exception is thrown:
ERROR [22023] [IBM][DB2] SQL0310N SQL statement contains too many
host variables.
I don't think the exception texts are correct...
Is this by design?
In COBOL the SQL code of -310 is returned, which is "DECIMAL HOST VARIABLE OR PARAMETER number CONTAINS NON-DECIMAL DATA".
Turns out the error -310 returned to the COBOL test program was the one to look at.
So we changed the DECIMAL TO NUMBER in the COBOL stored procedure and now we get back parameters instead of an exception.
Still don't know why this was a problem only when the select does not find any records. I only look at the c# side of the world. Oy veh!
I have an oracle package with a procedure that has a in out reference cursor. My understanding is that this is pretty standard.
What I didn't like is the fact that I had to write a ton of code to just see the output. So I asked this question and it turns out I can get what I want by creating a function that wraps the procedure.
Update: Looks like I don't need the function anymore but it may be worth knowing anyway for those curious see the original question and answer updates.
Here's the function
FUNCTION GetQuestionsForPrint (user in varchar2)
RETURN MYPACKAGE.refcur_question
AS
OUTPUT MYPACKAGE.refcur_question;
BEGIN
MYPACKAGE.GETQUESTIONS(p_OUTPUT => OUTPUT,
p_USER=> USER ) ;
RETURN OUTPUT;
END;
and here's what I do to execute it in SQL Developer
var r refcursor;
exec :r := mypackage.getquestionsForPrint('OMG Ponies');
print r;
So from now on I'm probably going to add the ForPrint functions to all my procedures.
This got me thinking, maybe functions are what I want and I don't need procedures.
To test this I tried executing the function from .NET, except I can't do it. Is this really the way it is.
using (OracleConnection cnn = new OracleConnection("Data Source=Test;User Id=Test;Password=Test;"))
{
cnn.Open();
OracleCommand cmd = new OracleCommand("mypackage.getquestionsForPrint");
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add ( "p_USER", "OMG Ponies");
cmd.Connection = cnn;
OracleDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
Console.WriteLine(rdr.GetOracleValue(0));
}
Console.ReadLine();
}
So I get the error.
getquestionsForPrint is not a procedure or is undefined
I tried ExecuteScalar as well with the same result.
EDIT Taking Slider345's advice I've also tried setting the command type to text and using the following statement and I get
invalid SQL statement
mypackage.getquestionsForPrint('OMG Poinies');
and
var r refcursor; exec :r := mypackage.getquestionsForPrint('OMG Poinies');
Using Abhi's variation for the command text
select mypackage.getquestionsForPrint('OMG Poinies') from dual
resulted in
The instruction at "0x61c4aca5"
referenced memory at "0x00000ce1". The
memory could not be "read".
Am I just barking up the wrong tree?
Update
Attempting to add an output parameter doesn't help.
cmd.Parameters.Add(null, OracleDbType.RefCursor, ParameterDirection.Output);
Not sure what the name should be since its the return value of a function (I've tried null, empty string, mypackage.getquestionsForPrint) but in all cases it just results in
ORA-06550: line 1, column 7:
PLS-00306: wrong number or types of
arguments in call to
'getquestionsForPrint'
Final Edit (hopefully)
Apparently Guddie asked a similar question 3 months after I did. He got the answer which is to
Set your command text to an anonymous block
Bind a parameter to the ref cursor setting the direction to output
Call Execute non reader.
Then use your parameter
using (OracleConnection cnn = new OracleConnection("Data Source=Test;User Id=Test;Password=Test;"))
{
cnn.Open();
OracleCommand cmd = new OracleCommand("mypackage.getquestionsForPrint");
cmd.CommandType = CommandType.Text;
cmd.CommandText = "begin " +
" :refcursor1 := mypackage.getquestionsForPrint('OMG Ponies') ;" +
"end;";
cmd.Connection = cnn;
OracleDataAdapter da = new OracleDataAdapter(cmd);
cmd.ExecuteNonQuery();
Oracle.DataAccess.Types.OracleRefCursor t = (Oracle.DataAccess.Types.OracleRefCursor)cmd.Parameters[0].Value;
OracleDataReader rdr = t.GetDataReader();
while(rdr.Read())
Console.WriteLine(rdr.GetOracleValue(0));
Console.ReadLine();
}
I have not tested this with a function, but for my stored procedures. I specify the out parameter for the refCursor.
command.Parameters.Add(new OracleParameter("refcur_questions", OracleDbType.RefCursor, ParameterDirection.Output));
If you are able to get the function to work with the CommandType.Text. I wonder if you can try adding the parameter above except with the direction as:
ParameterDirection.ReturnValue
I am using Oracle.DataAccess version 2.111.6.0
I had to go up and down between the question and answers to figure out the full code that works. So I am giving the full code here that worked for me for others -
var sql = #"BEGIN :refcursor1 := mypackage.myfunction(:param1) ; end;";
using(OracleConnection con = new OracleConnection("<connection string>"))
using(OracleCommand com = new OracleCommand())
{
com.Connection = con;
con.Open();
com.Parameters.Add(":refcursor1", OracleDbType.RefCursor, ParameterDirection.Output);
com.Parameters.Add(":param1", "param");
com.CommandText = sql;
com.CommandType = CommandType.Text;
com.ExecuteNonQuery();
OracleRefCursor curr = (OracleRefCursor)com.Parameters[0].Value;
using(OracleDataReader dr = curr.GetDataReader())
{
if(dr.Read())
{
var value1 = dr.GetString(0);
var value2 = dr.GetString(1);
}
}
}
Hope it helps.
I know this is quite an old post, but since it took me so long to figure out all of the minutia involved in getting .NET to "fight nice" with Oracle, I figured I'd put this advice out there for anyone else in this sticky situation.
I frequently call Oracle stored procedures that return a REF_CURSOR in our environment (.NET 3.5 against Oracle 11g). For a function, you can indeed name the parameter anything you'd like, but then you need to set its System.Data.ParameterDirection = ParameterDirection.ReturnValue then ExecuteNonQuery against the OracleCommand object. At that point the value of that parameter will be the ref_cursor that the Oracle function returned. Just cast the value as an OracleDataReader and loop through the OracleDataReader.
I'd post the full code, but I wrote the data access layer in VB.NET years ago, and the bulk of the code consuming the data access layer (our corporate intranet) is in C#. I figured mixing languages in a single response would be the larger faux pas.
Can anyone tell me how I can control the output from an SQL stored procedure that returns more than one set of output?
I am currently doing the following:
DataTable allData = new DataTable();
SqlConnection connection = new SqlConnection(mySource);
SqlCommand cmd = new SqlCommand(procedureName, connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(paramName, SqlDbType.Int);
cmd.Parameters[paramName].Value = paramValue;
connection.Open();
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
adapter.Fill(allData);
connection.Close();
Now this works fine if the procedure has only one output value, but how do I deal with the following:
My stored procedure broadly does the following:
It calls a number of other stored procedures in order to construct a dynamic SQL query (lets call this #query) and then calls EXECUTE(#query) which does a SELECT.
Using the code snippet above returns the result from the SELECT query, which is fine. But what I would also like it so have the string #query returned. I can specify it as an output type and let SQL fetch it, but how do I access it from c#? (Actually, more specifically, when I do this the code snipped above returns only the string #query and no longer returns the results of the SELECT)
Thanks
Karl
You can do like this:
DataSet allData = new DataSet ();
...
...
...
adapter.Fill(allData);
then each result of the select is in different dataTable
Using SqlDataReader.NextResult .
This little bit shifts you from using SqlDataAdapter, but you still is able to populate DataTable with DataTable.Load