I seem have come across a short coming with ODBC and DB2 when running stored procedures. It seems that it is not possible to return data from a stored procedure, and I have a prexisting query that I need to use. Has anyone got around this particular issue?
Thanks in advance
Update
The code that I am calling is as follows (assuming that the connection is already opened):
string BaseSQL = "CALL B6009822.O#04666803.PUT";
OdbcCommand command = new OdbcCommand(BaseSQL, myConnection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("#Owner", OdbcType.VarChar).Value = "MH";
int rows = command.ExecuteNonQuery();
myConnection.Close();
I get the following error ..
ERROR [HY000] [IBM][System i Access ODBC Driver][DB2 for i5/OS]SQL0440 - Routine PUT in O#04666803 not found with specified parameters.
It seems to be objecting to the catalog/library and procedure name. Any idea on what I need to do to get the procedure called ?
Second Update - real example
string BaseSQL = "{ CALL B6009822.O#04666803.PUT(?,?,?,?,?,?,?,?,?) }";
OdbcCommand command = myConnection.CreateCommand();
command.CommandText = BaseSQL;
//OdbcCommand command = new OdbcCommand(BaseSQL, myConnection);
command.CommandType = CommandType.StoredProcedure;
/*
#Param1 VarChar(4), #Param2 dec(8,0),
#Param3 dec(4,0), #Param4 dec(8,0),
#Param5 VarChar(60), #Param6 dec(9,2),
#Param7 dec(9,0), #Param8 dec(9,2),
#Param9 VarChar(10))
*/
command.Parameters.Add("#Param1", OdbcType.VarChar, 4).Value = "MH";
command.Parameters.Add("#Param2", OdbcType.Decimal, 8).Value = 20110217;
command.Parameters.Add("#Param3", OdbcType.Decimal, 4).Value = 1;
command.Parameters.Add("#Param4", OdbcType.Decimal, 8).Value = 178377;
command.Parameters.Add("#Param5", OdbcType.VarChar, 60).Value = "Description";
command.Parameters.Add("#Param6", OdbcType.Decimal, 9).Value = 0;
command.Parameters.Add("#Param7", OdbcType.Decimal, 9).Value = 45;
command.Parameters.Add("#Param8", OdbcType.Decimal, 9).Value = 0;
command.Parameters.Add("#Param9", OdbcType.VarChar, 10).Value = "*CREATE";
int rows = command.ExecuteNonQuery();
myConnection.Close();
Have you tried the CALL syntax?
http://publib.boulder.ibm.com/infocenter/dzichelp/v2r2/index.jsp?topic=/com.ibm.db29.doc.odbc/db2z_odbcspcall.htm
EDIT: Just in case my comment above is correct:
From this resource, have you tried:
string BaseSQL = "CALL B6009822.O#04666803.PUT (?)";
Important: Unlike ODBC, DB2 ODBC does
not support literals as procedure
arguments. You must use parameter
markers to specify a procedure
parameter.
First of all I had to make the library (or whatever the proper term is) visible in the "System i Navigator". It seems that there is an issue with stored procedures that return results, so I had to call it as a SELECT, as shown below ...
"SELECT PUT('{4}',{1},1,{0},'{2}',0,{3},0,'{5}') as A from LIBRARY.EARNER where EAR = '{4}'"
Not sure if this helps anyone else other than me though!
For any DB2 n00bs like myself hitting this page searching for answers to "SQL0440 - Routine [YourRoutine] in [*N | LIBRARY] not found with specified parameters"...
For me, my problem was where I had defined the constant for the stored procedure name, like so:
// Database Constants
public const string DB_PROC_GET_MYPROC = #"LIBRARY.MYPROC";
How I should have declared it was:
// Database Constants
public const string DB_PROC_GET_MYPROC = #"LIBRARY.MYPROC(#stateCode, #productCode, #tranType)";
...with the parameters included.
I've spent so long with Entity Framework, I have all but forgotten direct database connection basics in c#.
Hope this saves someone some time.
Related
I've created a procedure for test this problem and it works right in oracle developer. There is a typed named "dizi" (array and varchar2). And procedure has input parameter. I'm trying to pass an array to this to this procedure as a parameter in c#. I've searched a lot but i couldn't solve the problem. The error is: "Not all veriables bound"
public void InsertQuestion(List<string> area_list)
{
quest_areas = area_list.ToArray();
command = new OracleCommand();
command.Connection = connect;
connect.Open();
var arry = command.Parameters.Add("area_array",OracleDbType.Varchar2);
arry.Direction = ParameterDirection.Input;
arry.Size = quest_areas.Length;
arry.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
arry.Value = quest_areas;
command.BindByName = true;
command.CommandText ="TESTPROCEDURE(:area_array)";
command.CommandType = CommandType.StoredProcedure;
command.ExecuteNonQuery();
connect.Close();
}
Here is my procedure (it is just for test but i'll use something like that)
CREATE OR REPLACE PROCEDURE TESTPROCEDURE (t_in IN dizi)
IS
BEGIN
FOR i IN 1..t_in.count LOOP
dbms_output.put_line(t_in(i));
END LOOP;
END;
I've got code that successfully passes array down to oracle sprocs. Takes a slightly different approach to yours. Not entirely sure how much is relevant, but in case it helps my code:
uses the correct name parameter name (t_in in your case)
doesn't bother setting the size of the parameter
creates an object array that is the correct length and copies the contents across into it (ie from quest_areas in your case)
then sets this object array as the Value for the command parameter
doesn't use bind variables when calling the proc, rather just uses the proc name by itself as the CommandText.
That said, I suspect your problem might be around your use of a bind variable when calling the procedure. What happens if you just set 'TESTPROCEDURE' as the CommandText?
Or go the other way and put change it into a proper anonymous PLSQL block 'begin TESTPROCEDURE(:area_array); end;' and change the CommandType to CommandType.Text (as just suggested by Wernfried while I was typing...)
Update
public void InsertQuestion(List<string> area_list)
{
var input_array = area_list.Select(s => (object)s).ToArray();
command = new OracleCommand();
command.Connection = connect;
connect.Open();
var arry = command.Parameters.Add("area_array",OracleDbType.Varchar2);
arry.Direction = ParameterDirection.Input;
arry.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
arry.Value = input_array;
command.CommandText ="TESTPROCEDURE";
command.CommandType = CommandType.StoredProcedure;
command.ExecuteNonQuery();
connect.Close();
}
I have a stored procedure that correctly returns records when I call it from a SSMS query.
Here is the stored procedure:
CREATE PROCEDURE [dbo].[q_CheckRecords]
#ItemIDS AS VARCHAR(40)
AS
BEGIN
SET NOCOUNT ON
SELECT *
FROM q_Warehouse80_OOS_ItemsNeedingNotification
WHERE item_id = #ItemIDS
END
Calling this from a SSMS query like this:
exec [q_CheckOOSWarehouse80ItemsNeedingNotification] 'B30-R10000-B001'
It correctly returns a row, however when I use this C# code to call the stored procedure, I never get any rows returned.
SqlCommand cmd = null;
SqlDataReader myReader = null;
System.Data.SqlClient.SqlConnection conn = null;
conn = new System.Data.SqlClient.SqlConnection("Data Source=" + sSessionServer + ";database=" + sSessionDatabase + "; Integrated Security=SSPI");
String SQL = "[q_CheckOOSWarehouse80ItemsNeedingNotification]";
cmd = new SqlCommand();
cmd.CommandText = SQL;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Connection = conn;
cmd.Parameters.Add("#ItemIDS", SqlDbType.VarChar).Value = ItemsToBeChecked;
conn.Open();
myReader = cmd.ExecuteReader();
// check to see if any rows were returned.
if (myReader.HasRows)
{
while (myReader.Read())
{
// code to read fields in returned rows here
}
}
conn.Close();
It appears to be a problem with how C# defines the datatype being passed to the stored procedure, but I haven't found any information online on how to solve this problem.
If I were to changed the stored procedure so it's "hard coded"
#ItemIDS AS VARCHAR(40)
AS
BEGIN
SET NOCOUNT ON
select * from q_Warehouse80_OOS_ItemsNeedingNotification where item_id = 'B30-R10000-B001'
END
then the C# call to it correctly indicates that a row was "found".
Any help would be greatly appreciated.
When you don't specify the length of a varChar sql treats it as length 1.
cmd.Parameters.Add("#ItemIDS", SqlDbType.VarChar).Value = ItemsToBeChecked;
Your variable ItemsToBeChecked will be truncated, and I suspect there is nothing matching in your database with just the first character of that value.
Specify the length of the varchar
cmd.Parameters.Add("#ItemIDS", SqlDbType.VarChar, 40).Value = ItemsToBeChecked;
You can verify this is the case by putting a profiler on sql, and executing your c#. You will see the value passed to the #ItemIDS parameter is only 1 character long.
The issue you are facing is because you are not calling your stored procedure in your C# Code.
I have a stored procedure that goes like this:
ALTER PROCEDURE [dbo].[AuthenticateUser]
#AzUserName varchar(20),
#Hash varchar(32),
#UserId bigint output,
#Authorized bit output
...
and runs just fine fine in Management Studio.
Here's my C# code:
SqlConnection scon = new SqlConnection(connectionString);
SqlCommand authCmd = new SqlCommand("AuthenticateUser", scon);
authCmd.CommandType = System.Data.CommandType.StoredProcedure;
SqlParameter userNameParam = authCmd.Parameters.Add("#AzUserName", System.Data.SqlDbType.VarChar, 20);
userNameParam.Value = username;
string hashed = Md5Hash.ComputeHash(username);
SqlParameter hashedParam = authCmd.Parameters.Add("#Hash", System.Data.SqlDbType.VarChar, 32);
hashedParam.Value = hashed;
SqlParameter userIdParam = authCmd.Parameters.Add("#UserId", System.Data.SqlDbType.Int);
userIdParam.Direction = System.Data.ParameterDirection.ReturnValue;
SqlParameter authorizedParam = authCmd.Parameters.Add("#Authorized", System.Data.SqlDbType.Bit);
authorizedParam.Direction = System.Data.ParameterDirection.ReturnValue;
scon.Open();
authCmd.ExecuteNonQuery();
scon.Close();
When I run it I am getting the following error:
{"Procedure or function 'AuthenticateUser' expects parameter '#UserId', which was not supplied."} System.Exception {System.Data.SqlClient.SqlException}
When I replace ParameterDirection.ReturnValue with ParameterDirection.Output I am not getting the error but never get the value of the procedure.
UPDATE:
Thank you All for your help. The error was more trivial than you would have thought and I described in the question. I have been changing back and forth ReturnValue to Output for quite a while today with no result. Then I had to post my question on SO just to realize that I am taking the hash value of ... username..Going outdoor to get some oxygen now.
You will have to use ParameterDirection.Output on every parameter, that has been marked with output in T-SQL. You can access the values, after the call to
authCmd.ExecuteNonQuery();
by getting the values of the parametes like this:
authCmd.Parametes["#UserId"].Value
You're confusing the concepts of OUTPUT and RETURN values.
A RETURN value from a stored procedure is a single integer value per stored procedure that is defined within your proc by using the RETURN statement eg
RETURN 1
A stored procedure can have zero to many parameters of which zero to many can be defined as OUTPUT.
In your case you're not showing any use of the RETURN statement but you are using OUTPUT parameters. In SQL Server these are more like input/output parameters and you need to provide a value.
You can access the resulting value of an OUTPUT parameter by looking at the parameters collection after calling the stored procedure and look at the value eg
authCmd.Parameters[2].Value
Or
userIdParam.Value
As per other answers, you need to use the output parameter direction to achieve this
You can access the values of authorizedParam.Value and userIdParam.Value after executing the command.
SqlConnection scon = new SqlConnection(connectionString);
SqlCommand authCmd = new SqlCommand("AuthenticateUser", scon);
authCmd.CommandType = System.Data.CommandType.StoredProcedure;
SqlParameter userNameParam = authCmd.Parameters.Add("#AzUserName", System.Data.SqlDbType.VarChar, 20);
userNameParam.Value = username;
string hashed = Zonal.Pie.Core.Common.Utils.Md5Hash.ComputeHash(username);
SqlParameter hashedParam = authCmd.Parameters.Add("#Hash", System.Data.SqlDbType.VarChar, 32);
hashedParam.Value = hashed;
SqlParameter userIdParam = authCmd.Parameters.Add("#UserId", System.Data.SqlDbType.Int);
userIdParam.Direction = System.Data.ParameterDirection.Output;
SqlParameter authorizedParam = authCmd.Parameters.Add("#Authorized", System.Data.SqlDbType.Bit);
authorizedParam.Direction = System.Data.ParameterDirection.Output;
scon.Open();
authCmd.ExecuteNonQuery();
//Access authorizedParam.Value and userIdParam.Value here
scon.Close();
I have an Informix database which exposes some stored procedures, I have an abstracted data accessor that handles communicating with them but I have a problem with a null value.
Directly you can call:
execute procedure some_stored_procedure(1,2,NULL,3)
and get back correct results, I would rather there not be this nullable field, but it is out of my hands. Anyway I was originally trying to call it like so:
var command = connection.CreateCommand();
command.CommandType = CommandTypes.StoredProcedure
command.CommandText = "some_stored_procedure"
// Pass in the parameters
However doing that causes Informix to throw a syntax error, so instead I have been forced to go with the:
var command = connection.CreateCommand();
command.CommandText = "execute procedure some_stored_procedure(?,?,?,?)";
// Pass in parameters
Which works but never passes back correct results, and if I try and make parameter 3 null it gives another syntax error. Am I missing something or is there a better way to call these stored procedures?
try parameterizing the parameters (you can use OdbcParameters if working with the odbcdriver) and then pass DbNull.Value where Null is required.
Try this:
var command = connection.CreateCommand();
command.CommandType = CommandTypes.Text;
command.CommandText = "call some_stored_procedure(?,?,?,?)";
command.Parameters.Add(param); //add all your parameters.
Format the query as follow:
strQuery = string.Format("EXECUTE PROCEDURE Cronos_UpdateStateLegacyProduct ({0})", oValidityProducts.PurchaseId);
OdbcConnection oConnection = new OdbcConnection(this.strConnectionString);
OdbcCommand oCommand = new OdbcCommand();
oCommand.CommandType = CommandType.StoredProcedure;
oCommand.CommandText = strQuery;
oCommand.Connection = oConnection;
oConnection.Open();
intResult = oCommand.ExecuteNonQuery();
Best Regards
In case you are using an ODBC connection, when using CommandType.StoredProcedure you must use a diferent syntax for the procedure's name. In your case:
CommandText = "{ CALL some_stored_procedure(?,?,?,?)}"
Check this link for more information: https://support.microsoft.com/en-us/kb/310130
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.