retrieving a value from a plsql select procedure - c#

From a table containing rfid strings (random varchar strings) I have to select a free rfid string that is not yet in used in another table, for that i use the following sql statement:
select * from (
select rfid
from rfid_col
where rfid not in (select rfid from person)
)where rownum<=1;
the sql on itself works fine but then I needed to put it in a stored procedure
create or replace
procedure getFreeRfid(rfidout out varchar2) is
begin
select * into rfidout
from (select rfid from rfid_col where rfid not in (select rfid from person)
)where rownum<=1;
end getFreeRfid;
this code works fine in oracle sql developer
to retrieve this in C# using the oracle data I use the following code
OracleCommand rfidcommand = db.connection.CreateCommand();
rfidcommand.CommandType = System.Data.CommandType.StoredProcedure;
rfidcommand.CommandText = "getFreeRfid";
rfidcommand.Parameters.Add(new OracleParameter("rfidout", OracleDbType.Varchar2)).Direction = System.Data.ParameterDirection.Output;
rfidcommand.ExecuteNonQuery();
string rfid = rfidcommand.Parameters["rfidout"].Value.ToString();
however somehow this throws:
A first chance exception of type 'Oracle.DataAccess.Client.OracleException' occurred in Oracle.DataAccess.dll
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "TIM.GETFREERFID", line 3
ORA-06512: at line 1
I have been struggling with this problem for quite some time now and this is my last solution

It is important to set size for strings when dealing with output parameters. The size sets automatically on Input parameters because it is known. I don't know if this will fix your issue but you will not get value if size not set on the output, not a full value anyway - you may get 1 character. And make sure that type in Db is indeed Varchar2
Please, try this code:
string connStr = ".........";
string result = null;
using (OracleConnection conn = new OracleConnection(connStr))
{
conn.Open();
using (OracleCommand comm = new OracleCommand("getFreeRfid", conn))
{
comm.CommandType = CommandType.StoredProcedure;
OracleParameter p = new OracleParameter("rfidout", OracleDbType.Varchar2, ParameterDirection.Output);
p.Size = 200;
comm.Parameters.Add(p);
comm.ExecuteNonQuery();
result = (string)comm.Parameters[0].Value;
}
}
Good luck!
PS: using will help you close and dispose of command and connection.

Related

PL/SQL - Oracle: Stored procedure returning dataset from various tables

What I need:
In PLS/SQL on an Oracle DB, create a stored procedure or function with parameters, which given a declared table of , where is a ROW of a table (with all the fields), returns the resultset following the conditions given in the parameters. After, I need to call them from Microsoft Entity Framework with edmx file.
Basically the need is to being able to provide a quick report of the table contents into a pdf, matching some filters, with an oracle db.
The mantainer must be able, provided a script I give, to create and add new reports, so this needs to be dynamic.
Here's what I've got so far:
CREATE OR REPLACE type THETABLEIWANTTYPE as table of THETABLEIWANT%TYPE
create function
SCHEMA.THETABLEIWANT_FUNCTION(PARAM_GR in number default 1)
return THETABLEIWANTTYPE
PIPELINED
as
result_table THETABLEIWANTTYPE
begin
SELECT S.id, S.idg, S.sta, S.tab
Bulk collect into result_table
from SCHEMA.THETABLEIWANT S
WHERE IDGR = PARAM_GR
IF result_table.count > 0 THEN
for i in result_table.FIRST .. result_table.LAST loop
pipe row (result_table(i))
end loop
end if
return
end;
But it's not working. It gives errors.
Running CREATE TYPE I get:
Compilation errors for TYPE SCHEMA.THETABLEIWANT
Error: PLS-00329: schema-level type has illegal reference to
SCHEMA.THETABLEIWANT
The mantainer will launch the script creating a TYPE of the row of the table I need, then the function should return a table with the records.
Then calling it from Entity Framework I should be able to execute it like I'm calling a normal select from my table, IE:
``_dbContext.THETABLEIWANT.Where(x => x.IDGR = Param_gr).ToList();
The problem is that mantainers should be able to generate new kind of reports with any select inside without the need of my intervention on the software code.
Any hint?
It's ok also to bulk all the select result into a temp table but it has to be dynamic as column will be changing
I ended up to write a PLS/SQL procedure that returns a cursor and managing it from C# code with Oracle.ManagedDataAccess Library.
Here's the procedure, for anyone interested:
CREATE OR REPLACE PROCEDURE SCHEMA.PROC_NAME(
PARAM_1 VARCHAR2,
RESULT OUT SYS_REFCURSOR)
IS
BEGIN
OPEN RESULT FOR
SELECT A, V, C AS MY_ALIAS from SCHEMA.TABLE WHERE FIELD = PARAM_1 AND FIELD_2 = 'X';
END;
And here's the C# code for calling and getting the result:
OracleConnection conn = new OracleConnection("CONNECTIONSTRING");
try
{
if (conn.State != ConnectionState.Open)
conn.Open();
List<OracleParameter> parametri = new List<OracleParameter>()
{
new OracleParameter
{
ParameterName = nameof(filter.PARAM_1),
Direction = ParameterDirection.Input,
OracleDbType = OracleDbType.NVarchar2,
Value = filter.PARAM_1
}
};
OracleCommand cmd = conn.CreateCommand();
cmd.Parameters.AddRange(parametri.ToArray());
OracleParameter cursor = cmd.Parameters.Add(
new OracleParameter
{
ParameterName = "RESULT",
Direction = ParameterDirection.Output,
OracleDbType = OracleDbType.RefCursor
}
);
cmd.CommandText = procedureName;
cmd.CommandType = CommandType.StoredProcedure;
cmd.ExecuteNonQuery();
using (OracleDataReader reader = ((OracleRefCursor)cursor.Value).GetDataReader())
{
if (reader.HasRows)
while (reader.Read())
{
//Iterate the result set
}
}
}
catch(Exception ex)
{
//Manage exception
}

Stored Procedure doesn't return records when called from C#, but works in SSMS

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.

Error "Data too long for column" when executing stored procedure via c# mysql connection

I am writing a C# application that will access a MySQL Database using a stored procedure with 5 parameters:
in startTime varchar(20),
in endTime varchar(20),
in tagID Int,
in FullDay Int,
in iteration Int
that returns 3 variables all integers.
When executing the MySQLCommand I get the error: "Data too long for column 'startTime' at row 41".
Here is my code:
//create command
MySqlCommand cmd = new MySqlCommand("GetArchiveData", connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("startTime", startIn);
cmd.Parameters["startTime"].Direction = ParameterDirection.Input;
cmd.Parameters.AddWithValue("endTime", endIn);
cmd.Parameters["endTime"].Direction = ParameterDirection.Input;
cmd.Parameters.AddWithValue("tagID", tagIn);
cmd.Parameters["tagID"].Direction = ParameterDirection.Input;
cmd.Parameters.AddWithValue("FullDay", fdIn);
cmd.Parameters["FullDay"].Direction = ParameterDirection.Input;
cmd.Parameters.AddWithValue("iteration", iterationIn);
cmd.Parameters["iteration"].Direction = ParameterDirection.Input;
if (cmd.Connection.State == ConnectionState.Closed)
{
cmd.Connection.Open();
}
MySqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
TagData tagData = new TagData();
tagData.TagID = tagIn;
tagData.Dsttimestamp = Convert.ToInt32(dr["timestamp"]);
tagData.PvValue = Convert.ToInt32(dr["sp"]);
tagData.PvValue = Convert.ToInt32(dr["pv"]);
tagDataList.Add(tagData);
}
dr.Close();
this.CloseConnection();
return tagDataList;
If anyone knows why this would happen and why the first occurrence would be row 41 it would be greatly appreciated.
UPDATE:
Removing the Single quotations that are required in the MySQL statement in workbench from the strings startIn and endIn has solved that issue. MySQL uses single quotes to define strings where the C# variable is already declared as a string.
It looks like your problem is in the sproc.
Have you looked at the vaklue you are passing in to it?
It just seems the value is too long for the column so looking at what is being passed will let you know for sure.
Inserting value is too long for the column.
Please debug and Check whether u are inserting correct type of data or correct type value for the column.
There is an hard limit on how much data can be stored in a single row of a mysql table, regardless of the number of columns or the individual column length.

SQL Server stored procedure that returns a boolean if table exists, c# implementation

I have created a stored procedure that takes a single argument, the name of a table, and returns 1 if it exists in the database, 0 if it does not. In SQL Server Management Studio testing my stored procedure works exactly as I'd like it to, however I'm having trouble getting that value for use in my C# program.
My options seem to be ExecuteScalar(), ExecuteNonQuery() or ExecuteReader(), none of which seem appropriate for the task, nor can I get them to even retrieve my stored procedure's result.
I have tried assigning my parameter with both cmd.Parameters.AddWithValue and cmd.Parameters.Add again to no avail.
Assuming you have a stored procedure like this which selects either a 0 (table does not exist) or 1 (table does exist)
CREATE PROCEDURE dbo.DoesTableExist (#TableName NVARCHAR(100))
AS
BEGIN
IF EXISTS (SELECT * FROM sys.tables WHERE Name = #TableName)
SELECT 1
ELSE
SELECT 0
END
then you can write this C# code to get the value - use .ExecuteScalar() since you're expecting only a single row, single column:
// set up connection and command
using (SqlConnection conn = new SqlConnection("your-connection-string-here"))
using (SqlCommand cmd = new SqlCommand("dbo.DoesTableExist", conn))
{
// define command to be stored procedure
cmd.CommandType = CommandType.StoredProcedure;
// add parameter
cmd.Parameters.Add("#TableName", SqlDbType.NVarChar, 100).Value = "your-table-name-here";
// open connection, execute command, close connection
conn.Open();
int result = (int)cmd.ExecuteScalar();
conn.Close();
}
Now result will contain either a 0 if the table doesn't exist - or 1, if it does exist.
Use this:
var returnParameter = cmd.Parameters.Add("#ReturnVal", SqlDbType.Int);
returnParameter.Direction = ParameterDirection.ReturnValue;
Your stored procedure should return 0 or 1.

Execute an oracle Function that returns a reference cursor in C#

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.

Categories