I have tried to execute the codes below to search for a username
using (var cmd = new NpgsqlCommand("search_users", conn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#username", NpgsqlTypes.NpgsqlDbType.Text, "xxx");
using (var reader = cmd.ExecuteReader())
while (reader.Read())
x.Add(reader["loginname"].ToString());
}
On the line of cmd.ExecuteReader() I'm getting this error
Npgsql.PostgresException: '42601: a column definition list is
required for functions returning "record"'
Am I doing the right way to read the returned records ? This is the stored procedure
CREATE FUNCTION search_users(username TEXT) RETURNS RECORD AS $$
DECLARE
ret RECORD;
BEGIN
SELECT * from public."Users" as t1
WHERE t1."LoginName" = username INTO ret;
RETURN ret;
END;$$ LANGUAGE plpgsql;
Any help is greatly appreciated.
If you want to return a record that matches the layout of public."Users" then I think all you need to do is specify that in the return type:
CREATE FUNCTION search_users(username TEXT) RETURNS public."Users" AS $$
DECLARE
ret RECORD;
BEGIN
SELECT * from public."Users" as t1
WHERE t1."LoginName" = username INTO ret;
RETURN ret;
END;$$ LANGUAGE plpgsql;
As far as the C# code... I see what you are trying to do, and I think if this were Oracle that approach would work perfectly. In PostgreSQL, the line between a function and a stored procedure is a little blurred (in my opinion), and the easiest way to execute the function and return the results is with a simple select statement. As such, your C# would look as follows:
using (var cmd = new NpgsqlCommand("select * from search_users(:USER_NAME)", conn))
{
cmd.Parameters.AddWithValue("USER_NAME", "xxx");
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
x.Add(reader["loginname"].ToString());
}
}
The use of the # versus the : totally doesn't matter... I only switched to the colon for personal style preferences.
Related
I have a Postgres database with a stored procedure that returns JSON documents, based on the article here: http://www.sqlines.com/postgresql/npgsql_cs_result_sets
The procedure is represented like this:
-- Procedure that returns a single result set (cursor)
CREATE OR REPLACE FUNCTION get_data_test() RETURNS refcursor AS $$
DECLARE
ref refcursor; -- Declare a cursor variable
BEGIN
OPEN ref FOR -- Open a cursor
SELECT row_to_json(r) AS data
FROM
(
SELECT *
FROM data AS d
) r;
RETURN ref; -- Return the cursor to the caller
END;
$$ LANGUAGE plpgsql;
I am then running the following code from a .net console app:
// Making connection with Npgsql provider
using (NpgsqlConnection conn = new NpgsqlConnection(connstring))
{
conn.Open();
var trans = conn.BeginTransaction();
var cmd = new NpgsqlCommand("get_data_test", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Transaction = trans;
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
Trace.WriteLine(reader[0]);
}
}
The output is a single instance wit the name of the refcursor, rather than the actual data:
< unnamed portal 1 >
If I run the select query directly in the command text, the result set is returned as expected. I've also tried explicitly calling the proc via text using "SELECT get_data_test()" but this also fails with just the cursor name.
I don't believe I am missing a step and this refcursor should be returned unbundled. What am I doing wrong?
As it happens, the tutorial was wrong:
Nice GitHub bug report: https://github.com/npgsql/npgsql/issues/1777
Correct answer:
-- Procedure that returns a single result set (cursor)
CREATE OR REPLACE FUNCTION get_data_test() RETURNS TABLE (data JSON) AS $$
BEGIN
RETURN query
SELECT row_to_json(r) AS data
FROM
(
SELECT *
FROM data AS d
) r;
END;
$$ LANGUAGE plpgsql;
I have a stored procedure that runs a select query through a cursor and returns the cursor (Original Select query is more complex, I have shortened it).
CREATE OR REPLACE PROCEDURE GETASSIGNEDROLES(IN V_USER_ID INTEGER) SPECIFIC GETASSIGNEDROLES DYNAMIC RESULT SETS 1 LANGUAGE SQL NOT DETERMINISTIC EXTERNAL ACTION READS SQL DATA CALLED ON NULL INPUT INHERIT SPECIAL REGISTERS
BEGIN
DECLARE TEMP_CURSOR CURSOR WITH HOLD WITH RETURN TO CLIENT FOR
SELECT DISTINCT ROLE_ID, ROLE_NAME FROM ROLE ORDER BY ROLE_NAME FOR READ ONLY WITH UR;
IF V_USER_ID IS NOT NULL AND V_USER_ID > 0 THEN
OPEN TEMP_CURSOR;
END IF;
END;
Now, I could not find any example showing how to get values from a stored procedure that is returning a cursor. I believe it is the same as for every other stored procedure containing just select statement?
Also, my database is DB2 so the syntax is a little different. So far I have the following code after watching few questions on SO. But I didn't find any question using Datatable and passing parameters in CMD, is this correct?
DataTable DT = new DataTable();
using (var connection = new DB2Connection(ConnectionString))
{
//connection.Open(); //open/close connection will be done implicitely by the DataAdapter.
using (DB2Command cmd = connection.CreateCommand())
{
cmd.CommandText = "GETASSIGNEDROLES";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new DB2Parameter(DataBaseObjects.ParamVUserID, DB2Type.Integer)).Value = userDo.User_ID;
//cmd.CommandTimeout = CommandTimeout; //wait time before terminating the attempt to execute a command and generating an error in secs
using (var da = new DB2DataAdapter(cmd))
{
da.Fill(DT);
}
DT.TableName = TableName;
return DT;
}
}
It's necessary to study all of the related/linked pages of the Db2 Knowledge Center for your Db2-version that describe C# .net common language runtime procedures.
Start here and study every linked page, make the examples work on your environment.
If your Db2-server runs on Microsoft-Windows, or if you installed the Windows-specific Db2-samples with your full data-server-client (default location is:
\program files\ibm\sqllib\samples\dotnet\cs
) then you can see the DataTable examples for C# there, and also online in the Knowledge-Center - "DbDatMap.cs" and related files and "SpClient.cs" and its dependencies
Please check why you populate DataTable DT but return DTT.
If you debug your code you should be able to iterate over rows in DT after the da.Fill(DT); completes successfully.
A consoleApp using most of your syntax works fine for me, the only variations being that I used a pre-existing connection, and supply a varchar parameter to my stored procedure instead of integer (but that cannot make a difference).
So I finally got this to working. About the open cursors, apparently that's how you do it in DB2. Cursor is left open for the client application to read from. This link helped: https://bytes.com/topic/db2/answers/465403-how-do-well-anything-db2-sql
Stored Procedure used (modified from question):
CREATE OR REPLACE PROCEDURE GETASSIGNEDROLES(IN V_USER_ID INTEGER, IN V_BUSINESS_ENTITY_ID INTEGER) SPECIFIC GETASSIGNEDROLES DYNAMIC RESULT SETS 1 LANGUAGE SQL NOT DETERMINISTIC EXTERNAL ACTION READS SQL DATA CALLED ON NULL INPUT INHERIT SPECIAL REGISTERS
BEGIN
DECLARE TEMP_CURSOR CURSOR WITH HOLD WITH RETURN TO CLIENT FOR
SELECT DISTINCT ROLE_ID, ROLE_NAME FROM ROLE ORDER BY ROLE_NAME FOR READ ONLY WITH UR;
IF V_USER_ID IS NOT NULL AND V_USER_ID > 0 THEN
OPEN TEMP_CURSOR;
END IF;
END;
Function in C# to call stored procedure and save retrieved data in a data table:
public DataTable GetAssignedRolesForUser(string ConnectionString, string TableName, User userDo)
{
//LogManager.EventLog("Inside GetAssignedRolesForUser");
try
{
DataTable DTT = new DataTable();
//See if UserID exists check is required here
using (var connection = new DB2Connection(ConnectionString))
{
//connection.Open(); //open/close connection will be done implicitely by the DataAdapter.
using (DB2Command cmd = connection.CreateCommand())
{
cmd.CommandText = DataBaseObjects.spGetAssignedRoles;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new DB2Parameter(DataBaseObjects.ParamVUserID, DB2Type.Integer)).Value = userDo.User_ID;
cmd.Parameters.Add(new DB2Parameter(DataBaseObjects.ParamVBusinessEntityID, DB2Type.Integer)).Value = ConfigurationManager.AppSettings["BusinessEntity"];
//cmd.CommandTimeout = CommandTimeout; //wait time before terminating the attempt to execute a command and generating an error in secs
using (var da = new DB2DataAdapter(cmd))
{
da.Fill(DTT);
}
DTT.TableName = TableName;
return DTT;
}
}
}
catch (Exception)
{
//LogManager.EventLog("[Error] Inside GetAssignedRolesForUser: " + ex.Message);
throw;
}
}
Code to call the C# function:
DataTable DtRoles = <Call function here>
foreach (DataRow row in DtRoles.Rows)
{
list.Add(Convert.ToInt32(row["ROLE_ID"].ToString()));
}
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
}
I am making the switch from classic ASP to ASP.NET. And I am having some trouble doing some basic stuff that I used to do easily with the old method. Below is a handy ASP function that I used to execute scalar queries with a few lines.
FUNCTION ExecuteScalarParams(SQLQuery, Parameter_Array)
Set cmd1 = Server.CreateObject("ADODB.Command")
cmd1.ActiveConnection = con
cmd1.CommandText = SQLQuery
cmd1.CommandType = 1
FOR ParamCount = 0 TO UBOUND(Parameter_Array)
cmd1.Parameters(ParamCount) = Parameter_Array(ParamCount)
NEXT 'ParamCount
Set rstScalar = cmd1.Execute()
IF NOT rstScalar.EOF THEN
arrScalar = rstScalar.GetRows()
IF UBOUND(arrScalar,2) = 0 THEN
ExecuteScalarParams = arrScalar(0,0)
ELSE
ExecuteScalarParams = NULL
END IF
ELSE
ExecuteScalarParams = NULL
END IF
rstScalar.Close
Set rstScalar = Nothing
Set cmd1 = Nothing
END FUNCTION
I used to pass a SQL query with question marks as place holders for the parameters like this:
SELECT TOP 1 UserName FROM Members WHERE (Created>?) AND (AdminLevel=?);
I would then set up a parameters array and pass it on to the function:
MyArray = ARRAY("1-JAN-2012",1)
The parameters in the array would replace the question marks in the query string in the order they appear.
I am trying to mimic this function in C# but I am stuck in the part where I have to pass the parameters. So far I got to the point where I have to used named place holders such as #Created and #AdminLevel instead of the question marks and then I have to set up parameter objects like this:
SqlParameter param = new SqlParameter();
param.ParameterName = "#AdminLevel";
param.Value = 1;
Is there a way to pass the parameters without having to set the parameter names and simply use question marks and the order in which they appear to specify which parameter goes where?
edit: as pointed out by Dana the MSDN Docs for Parameters shows you need to use named parameters for SqlClient but can use positional parameters for OleDb/ODBC.
You can make adding parameters a lot easier by using the code below; it's the skeleton I use but I'm sure there's a better way of doing it.
You still need to used named parameters, but you can simulate your question marks to an extent by naming them #a, #b, #c.. - positional parameters are fine until you get more than a handful of parameters and you have to constantly count the number of question marks to figure out which parameter value is being applied where, often resulting in mistakes.
using (var con = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString))
{
con.Open();
{
using (var command = con.CreateCommand())
{
command.Connection = conn;
command.CommandText = "SELECT * FROM [dbo].[Table] WHERE [c1] = #a AND [c2] = #b";
command.Parameters.AddWithValue("#a", aVal);
command.Parameters.AddWithValue("#b", bVal);
command.CommandType = CommandType.Text;
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
///
}
}
else
{
///
}
}
}
}
}
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.