Can anyone show me a working example of using a cursor returned from PLSQL to C# code?
I found many examples showing how to fill a dataSet with returned data, but I cannot find how to use that cursor with a DataReader, so as a result I have {unnamed portal}.
NpgsqlTransaction tr = (NpgsqlTransaction) Connection.BeginTransaction();
NpgsqlCommand cursCmd = new NpgsqlCommand("someStoredProcedure(:inRadius)", (NpgsqlConnection) Connection);
cursCmd.Transaction = tr;
NpgsqlParameter rf = new NpgsqlParameter("ref", NpgsqlTypes.NpgsqlDbType.Refcursor);
rf.Direction = ParameterDirection.InputOutput;
cursCmd.Parameters.Add(rf);
I have to add this to use NpgsqlDataReader myReader; correctly:
tr.Commit();
When I wrote fetch after the sql command, it works but it is not suitable.
For your reference:
/// <summary>
/// Get data from the returning refcursor of postgresql function
/// </summary>
/// <param name="FunctionName">Function name of postgresql</param>
/// <param name="Parameters">parameters to pass to the postgresql function</param>
/// <param name="ErrorOccured">out bool parameter to check if it occured error</param>
/// <returns></returns>
public List<DataTable> GetRefCursorData(string FunctionName, List<object> Parameters, out bool ErrorOccured)
{
string connectstring = ""; //your connectstring here
List<DataTable > dtRtn =new List<DataTable>();
NpgsqlConnection connection = null;
NpgsqlTransaction transaction = null;
NpgsqlCommand command = null;
try
{
connection = new NpgsqlConnection(connectstring);
transaction = connection.BeginTransaction();
command = new NpgsqlCommand();
command.Connection = connection;
command.CommandType = CommandType.StoredProcedure;
command.CommandText = FunctionName;
command.Transaction = transaction;
//
if (Parameters != null)
{
foreach (object item in Parameters)
{
NpgsqlParameter parameter = new NpgsqlParameter();
parameter.Direction = ParameterDirection.Input;
parameter.Value = item;
command.Parameters.Add(parameter);
}
}
//
NpgsqlDataReader dr = command.ExecuteReader();
while (dr.Read())
{
DataTable dt = new DataTable();
command = new NpgsqlCommand("FETCH ALL IN " + "\"" + dr[0].ToString() + "\"", Connection); //use plpgsql fetch command to get data back
NpgsqlDataAdapter da = new NpgsqlDataAdapter(command);
da.Fill(dt);
dtRtn.Add(dt); //all the data will save in the List<DataTable> ,no matter the connection is closed or returned multiple refcursors
}
ErrorOccured = false;
transaction.Commit();
}
catch
{
//error handling ...
ErrorOccured = true;
if (transaction != null) transaction.Rollback();
}
finally
{
if (connection != null) connection.Close();
}
return dtRtn;
}
I have got some answers on my question.
Problem: I have a stored PLSQL procedure which returns refCursor. I have to get the returned data with a DataReader, but wwhen I added parameters, the db returned <unnamed portal>.
To walk through all returned data I have to write my code like so:
NpgsqlTransaction tr = (NpgsqlTransaction) Connection.BeginTransaction();
NpgsqlCommand cursCmd = new NpgsqlCommand("someStoredProcedure", (NpgsqlConnection) Connection);
cursCmd.Transaction = tr;
NpgsqlParameter rf = new NpgsqlParameter("ref", NpgsqlTypes.NpgsqlDbType.Refcursor);
rf.Direction = ParameterDirection.InputOutput;
cursCmd.Parameters.Add(rf);
NpgsqlParameter param2 = new NpgsqlParameter("param1", NpgsqlTypes.Int32);
rf.Direction = ParameterDirection.Input;
cursCmd.Parameters.Add(param2);
NpgsqlDataReader r = cmd.ExecuteReader();
while (r.Read())
{
; // r.GetValue(0);
}
r.NextResult();
while(r.Read())
{
;
}
tr.Commit();
Notice that you don't write your parameters in sql like func(:param1).
If you have parameters in your function, assign only the function name to the CommandText property and add parameters to the NpgsqlCommand.Parameters collection as usual. Npgsql will take care of binding your parameters correctly.
But now I have another problem. When I pass another output parameter to my CommandText, I have two fields in my result. One of them is 0{my first output param} and the other is <unnamed portal>.
In Oracle, I can directly convert a RefCursor parameter to a DataReader, but in postgresql, I cannot.
First of all, here is some documentation that could be useful: Npgsql doc
In this documentation you'll find a NpgsqlDataAdapter. This object also has a Fill() method (inherited from DbDataAdapter). This method can take a DataSet and a cursor. It will fill the DataSet with the data returned by your cursor.
You can't actually give a DataReader to this method, but you can give a DataTable, I think you can manage to do something with this.
I have solved the problem with Out parameters by using two commands in the same transaction.
In the first command, I read the out parameter and then execute the next command.
The second command looks like:
var cmd2 = new NpgsqlCommand("FETCH ALL FROM \"list\"", (NpgsqlConnection) Connection)
Where list the name of cursor created inside the stored procedure. As a result I get data selected from the db.
Related
I'm trying to fill a DataSet object with an OracleCommand object, I just started using Oracle, I've using SQL Server all my life.
On my NET console project I added the NuGet Oracle.ManagedDataAccess package and changed Sql objects to Oracle. Like: SqlConnection to OracleConnection.
In SQL Server it works perfectly, but in Oracle it gives me this error I don't really understand what it means.
I changed SqlConnection to OracleConnection
and SqlCommand to OracleCommand
also
command.Parameters.AddWithValue("#" + parameters[i], values[i] ?? DBNull.Value);
to
command.Parameters.Add(parameters[i], values[i] ?? DBNull.Value);
command.Parameters[i].Value = values[i];
Because AddWithValue doesn't exist on OracleCommand
This is the method that gets the data from the db.
private void GetData(string storedProcedure, IReadOnlyList<string> parameters, IReadOnlyList<object> values)
{
using (var connection = new OracleConnection(_connectionString))
{
using (
var command = new OracleCommand(storedProcedure, connection)
{
CommandType = CommandType.StoredProcedure
})
{
if (parameters != null)
for (var i = 0; i < parameters.Count; i++)
{
command.Parameters.Add(parameters[i], values[i] ?? DBNull.Value);
}
var ds = new DataSet();
connection.Open();
new OracleDataAdapter(command).Fill(ds);
_data = ds.Tables;
connection.Close();
}
}
}
These are the parameters im using.
var db = new Connector.Provider("AIA.GET_DATA",
new[]{
"Test1",
"Test2",
"Test3",
"Test4"},
new object[]{
1,
2,
3,
null});
And this is the stored procedure.
PROCEDURE GET_DATA(
Test1 in NUMBER,
Test2 in NUMBER,
Test3 in NUMBER,
Test4 in NUMBER,
TestOut out SYS_REFCURSOR
);
On the constructor of Provider it gets the connection string and uses method GetData.
It fails on:
new OracleDataAdapter(command).Fill(ds);
Oracle.ManagedDataAccess.Client.OracleException: 'ORA-03115: unsupported network datatype or representation'
Again, this works perfectly on SQL Server.
Any help is appreciated, at least, what does this error message means.
EDIT:
Thank you Luke Woodward, that was very helpful. So there was a problem with the OUT parameter, but also with the type of the parameters I was sending. So that solves the problem.
Anyway, I ended up with this new method. Which is working fine, except with nulls.
private void GetData(string storedProcedure, IReadOnlyList<string> parameters, IReadOnlyList<object> values, IReadOnlyList<string> cursors)
{
using (var connection = new OracleConnection(_connectionString))
{
using (
var command = new OracleCommand(storedProcedure, connection)
{
CommandType = CommandType.StoredProcedure
})
{
if (parameters != null)
for (var i = 0; i < parameters.Count; i++)
{
var parameter = new OracleParameter();
parameter.ParameterName = parameters[i];
if (values[i] is Enum)
parameter.Value = (int)values[i];
else
parameter.Value = values[i];
if (cursors != null && cursors.Contains(parameter.ParameterName))
{
parameter.Direction = ParameterDirection.Output;
parameter.OracleDbType = OracleDbType.RefCursor;
}
else
{
parameter.OracleDbType = GetOracleType(values[i]);
}
command.Parameters.Add(parameter);
}
var ds = new DataSet();
connection.Open();
new OracleDataAdapter(command).Fill(ds);
_data = ds.Tables;
connection.Close();
}
}
}
In Sql I could use DBNull.Value, but in Oracle I need to define OracleDbType, which is anonoying since this method worked for any object, not caring about the type, now I still don't know how to fix it to make it work with any object in Oracle. But that could be considered offtopic, this question could be marked as answered.
You need to add an OracleParameter for the OUT parameter TestOut but you are not doing this.
Add these lines after the other lines that set up the parameters:
var outParam = new OracleParameter("TestOut", OracleDbType.RefCursor, ParameterDirection.Output);
command.Parameters.Add(outParam);
You will then need to execute the command separately rather than pass the command to the OracleDataAdapter. Do this by adding the line
command.ExecuteNonQuery();
immediately after the two that add outParam.
Finally, fill the dataset from the ref cursor in the OUT parameter by replacing the line
new OracleDataAdapter(command).Fill(ds);
with
new OracleDataAdapter().Fill(ds, (OracleRefCursor)outParam.Value);
Incidentally, I got a different error when I ran your code. I got the error PLS-00306: wrong number or types of arguments in call to 'GET_DATA'.
I know how to pass one parameter to an sql query but i want to create a function to pass multiple params that will have differents type and here im stuck.
public List<T> RawSql<T>(string query, params object[] parameters)
{
var command = context.Database.GetDbConnection().CreateCommand();
command.CommandText = query;
command.CommandType = CommandType.Text;
SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "#bookId";
parameter.SqlDbType = SqlDbType.Int;
parameter.Value = parameters[0];
command.Parameters.Add(parameter);
var result = command.ExecuteReader())
return result;
}
Usage :
var rows = helper.RawSql("myStoreProc #bookId", x=> new Book { Id = (bool)x[0] }, bookId);
But how i can change the RawSql function to pass multiple parameters like this :
var rows = helper.RawSql("myStoreProc #bookId, #authorName", x=> new Book { Id = (bool)x[0] }, bookId, authorName);
I would also suggest using Dapper instead of reinventing the wheel - but if you can't for some reason, I would change the method signature to accept params SqlParameter[] parameters instead of params object[] parameters - and then all you need to do in the method is command.Parameters.AddRange(parameters);.
As Marc Gravel wrote in his comment - naming the parameters is going to be the biggest problem if you are simply using object[].
Here is a method I wrote to compare values from two different days:
public DataTable sqlToDTCompare(string conStr, string stpName, DateTime startDate, DateTime endDate, int percent)
{
//receives connection string and stored procedure name
//then returns populated data table
DataTable dt = new DataTable();
using (var con = new SqlConnection(conStr))
using (var cmd = new SqlCommand(stpName, con))
using (var da = new SqlDataAdapter(cmd))
{
cmd.Parameters.Add("#StartDate", SqlDbType.Date).Value = startDate;
cmd.Parameters.Add("#EndDate", SqlDbType.Date).Value = endDate;
cmd.Parameters.Add("#Percent", SqlDbType.Int).Value = percent;
cmd.CommandType = CommandType.StoredProcedure;
da.Fill(dt);
}
return dt;
}
This method then returns that data to a DataTable (was what I needed at time of writing). You would be able to use this , with modifying to be of better fit for your needs.
What you're looking to use is something along:
SqlCommand.Parameters.Add("#Param1", SqlDbType.Type).Value = param1;
SqlCommand.Parameters.Add("#Param2", SqlDbType.Type).Value = param2;
SqlCommand.Parameters.Add("#Param3", SqlDbType.Type).Value = param3;
.....
Where .Type in SqlDbType.Type can be changed to matche whatever SQL datatype you're needing (ex. SqlDbType.Date).
I have previously done implementations along these lines.
public IEnumerable<SampleModel> RetrieveSampleByFilter(string query, params SqlParameter[] parameters)
{
using(var connection = new SqlConnection(dbConnection))
using(var command = new SqlCommand(query, connection))
{
connection.Open();
if(parameters.Length > 0)
foreach(var parameter in parameters)
command.Parameters.Add(parameter);
// Could also do, instead of loop:
// command.Parameters.AddRange(parameters);
using(var reader = command.ExecuteReader())
while(reader != null)
yield return new Sample()
{
Id = reader["Id"],
...
}
}
}
I actually wrote an extension method to read the values returned back into my object, but this allows you to pass a query and a series of parameters to simply return your object.
I would look into Dapper, saves a lot of time. But I find the problem with trying to reuse with the above type of solution creates a bit of tightly coupling often.
By doing this approach you push specific information about your query elsewhere, which separates logic directly out of the repository and tightly couples to another dependency and knowledge.
I have a following stored procedure:
create or replace PROCEDURE PRODUCT_DETAILS(p_code IN VARCHAR2,
cursorParam OUT SYS_REFCURSOR)
IS
BEGIN
OPEN cursorParam FOR
select str_auth_code, str_name
from strs
where str_auth_code = p_code;
END;
How can I call it with OrmLite? I've tryied:
connection.SqlList<Product>(#"EXEC PRODUCT_DETAILS #p_code", new { p_code = code });
but it throws an exception ORA-01036: illegal variable name/number
I just tried to do it with plain old ADO.NET and it worked:
using (var conn = new OracleConnection(connectionString))
{
OracleCommand cmd = new OracleCommand();
cmd.Connection = conn;
cmd.CommandText = "PRODUCT_DETAILS";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("p_code", OracleType.NVarChar).Value = redemptionCode;
cmd.Parameters.Add("cursorParam", OracleType.Cursor);
cmd.Parameters["cursorParam"].Direction = ParameterDirection.Output;
conn.Open();
OracleDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
Console.WriteLine(dr["Name"]);
}
conn.Close();
}
But I can't figure out how to do the same task with OrmLite.
What you have looks good. If you were concerned about the verbosity of the code, and were using a number of stored procedures, then you could use this extension method to remove some of the repeated code:
Extension Method:
public static class StoredProcExtensions
{
public static List<T> ExecStoredProcedure<T>(this IDbConnection connection, string procedureName, object parameters = null, string outputCursor = "cursorParam")
{
return connection.Exec(c => {
c.CommandText = procedureName;
c.CommandType = CommandType.StoredProcedure;
// Create the parameters from the parameters object
if(parameters != null)
foreach(var property in parameters.GetType().GetPublicProperties())
c.Parameters.Add(new OracleParameter(property.Name, property.GetValue(parameters)));
// Add the output cursor
if(outputCursor != null)
c.Parameters.Add(new OracleParameter(outputCursor, OracleDbType.RefCursor) { Direction = ParameterDirection.Output });
// Return the result list
return c.ExecuteReader().ConvertToList<T>();
});
}
}
Usage:
var download = connection.ExecStoredProcedure<ProductDownloads>(
"PRODUCT_DETAILS",
new { p_code = redemptionCode }
);
foreach (var productDownload in download)
{
Console.WriteLine(productDownload.Name);
}
So the arguments are:
Stored procedure name i.e. PRODUCT_DETAILS
Optional An object of input parameters i.e new { p_code = redemptionCode, other = "value" }
Optional The name of the output cursor - defaults to cursorParam
Note: this code is untested, because I don't have Oracle setup, but it does compile, and hopefully goes some way to simplifying your stored procedures.
So far ended up with following code:
using (var connection = factory.Open())
{
var download =
connection.Exec(c =>
{
c.CommandText = "PRODUCT_DETAILS";
c.CommandType = CommandType.StoredProcedure;
c.Parameters.Add(
new Oracle.DataAccess.Client.OracleParameter("p_code", Oracle.DataAccess.Client.OracleDbType.NVarchar2) { Value = redemptionCode });
c.Parameters.Add(
new Oracle.DataAccess.Client.OracleParameter("cursorParam", Oracle.DataAccess.Client.OracleDbType.RefCursor) { Direction = ParameterDirection.Output });
return c.ExecuteReader().ConvertToList<ProductDownloads>();
});
foreach (var productDownload in download)
{
Console.WriteLine(productDownload.Name);
}
}
But I think there should be a better way for doing this.
Late to the party, but this problem can be solved by simply adding BEGIN and END to the statement... it will work.
In my case, I was trying to refresh a materialized view, only after adding BEGIN and END will it work, otherwise it will throw OracleException ORA-00900: invalid SQL statement...
This should work:
db.ExecuteSql("BEGIN DBMS_SNAPSHOT.REFRESH('" + materializedViewName + "'); END;");
I have a webservice that searches the database for the stored templates. However, I get the error when running my application
Must declare the scalar variable "#Template".
[WebMethod]
public Verification StuVerification (byte[] Template)
{
cn.Open();
SqlCommand com = new SqlCommand("SELECT * FROM tblFingerprint WHERE Template = #Template)", cn);
SqlDataReader sr = com.ExecuteReader();
while (sr.Read())
{
Verification verification = new Verification()
{
StudentID = sr.GetInt32(0),
StudentNumber = sr.GetString(1),
Name = sr.GetString(2),
Surname = sr.GetString(3),
};
cn.Close();
return verification;
}
cn.Close();
return new Verification();
}
Verification ver = verification.StuVerification(m_VrfMin);
Verification v = new Verification();
if (ver.StudentID > 0)
{
// Verification v = new Verification();
richTextBox1.Text = v.StudentNumber;
}
else
{
richTextBox1.Text = "Verification Failed" + error;
}
You haven't added the SQL parameter to the SQLCommand:
com.Parameters.AddWithValue("#Template", TemplateObject);
The #Template string in your command text is a placeholder for a parameter that you should define in your command parameters collection together with a value to pass to the database code.
cn.Open();
SqlCommand com = new SqlCommand(#"SELECT * FROM tblFingerprint
WHERE Template = #Template", cn);
com.Parameters.AddWithValue("#Template", Template);
SqlDataReader sr = com.ExecuteReader();
Its value is used in the execution of your query to select the rows that will be returned by the query. However, it is not clear, from your code above what is the datatype of the field Template in your database table. As is, this code passes a byte array in the form of a binary datatype and this could not be the exact datatype to use for comparison against the Template field.
Seeing your comment about the Image field I could suggest to try with this (NOT TESTED)
SqlParameter p = com.Parameters.Add("#Template", SqlDbType.Image);
p.Value = Template;
SqlDataReader sr = com.ExecuteReader();
This seems to be necessary because adding a value of byte[] type with AddWithValue creates automatically a SqlDbType.Binary parameter type, instead the database seems to like an SqlDbType.Image, However, read about deprecated Image field
Try this.Add parameter for #template
SqlCommand com = new SqlCommand("SELECT * FROM tblFingerprint WHERE Template = #Template)", cn);
com.Parameters.AddWithValue("#Template", Template);
While using Visual Studio 2010 for my ASP.NET website, we have code for a stored procedure call:
SqlDataAccess sqlDataAccess = new SqlDataAccess();
SqlParameter[] parameters =
{
new SqlParameter("#R", rptType.Replace("'", "''")),
new SqlParameter("#M", hybrDct["mod"].ToString().Replace("'", "''")),
new SqlParameter("#C", hybrDct["CG"].ToString().Replace("'", "''")),
new SqlParameter("#Ts$", hybrDct["TFields"].ToString().Replace("'", "''")),
};
sqlDataAccess.ProcName = "MyStoredProc";
sqlDataAccess.Parameters = parameters;
Is it possible to get the execute printed out for debugging purposes instead of finding out each individual SqlParameter and typing it in individually?
Thanks.
I use something like the following - everything goes through this and gets logged - you get the idea.
/// <summary>
/// Executes a stored procedure with no return.
/// </summary>
/// <returns>The number of records affected by stored proc.</returns>
public static int ExecuteStoredProc(string storedProcName, params SqlParameter[] parameters)
{
StringBuilder callDefinition = new StringBuilder();
callDefinition.Append(string.Format("ExecuteStoredProc: {0} (", storedProcName));
for (int i = 0; i < parameters.Count(); i++)
{
callDefinition.Append(string.Format("{0}={1}", parameters[i].ParameterName, parameters[i].Value));
if (i < parameters.Count - 1)
{
callDefinition.Append(",");
}
}
callDefinition.Append(")";
log.Debug(callDefinition.ToString());
using (var ctx = ConnectionManager<SqlConnection>.GetManager(ConnectionProfile.ConnectionName))
{
using (SqlCommand command = new SqlCommand(storedProcName, ctx.Connection))
{
command.CommandType = System.Data.CommandType.StoredProcedure;
command.CommandTimeout = 1000;
foreach (SqlParameter parameter in parameters)
{
command.Parameters.Add(parameter);
//log your param here
}
return command.ExecuteNonQuery();
}
}
}
/// <summary>
/// Executes a query and returns a dataset
/// </summary>
public static DataSet ExecuteQueryReturnDataSet(string query, params SqlParameter[] parameters)
{
try
{
//implement the parameter logging here as in the above code sample as well
log.Debug("Executing ExecuteQueryReturnDataSet() calling query " + query);
using (var ctx = ConnectionManager<SqlConnection>.GetManager(ConnectionProfile.ConnectionName))
{
using (SqlCommand command = new SqlCommand(query, ctx.Connection))
{
command.CommandType = System.Data.CommandType.Text;
command.CommandTimeout = 1000;
foreach (SqlParameter parameter in parameters)
{
command.Parameters.Add(parameter);
}
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
DataSet dataSet = new DataSet();
adapter.Fill(dataSet);
return dataSet;
}
}
}
}
catch (Exception ex)
{
log.Error(ex);
throw;
}
}
I usually run the SQL Server Profiler (assuming that this is your database) and can copy the complete query from the log.
Unfortunately SqlClient (which I assume you are using to implement SqlDataAccess, which I also assume is your own data access layer) does not directly expose the commands sent to the SQL Server. What I typically do inside my data access layer is log all commands in a format that can be executed in a query window.