While using the using() {} (sic) blocks as shown below, and assuming that cmd1 does not live beyond the scope of the first using() {} block, why should the second block throw an exception with the message
The SqlParameter is already contained by another SqlParameterCollection
Does it mean that resources and/or handles - including the parameters (SqlParameterCollection) - attached to cmd1 are not released when its destroyed at the end of the block?
using (var conn = new SqlConnection("Data Source=.;Initial Catalog=Test;Integrated Security=True"))
{
var parameters = new SqlParameter[] { new SqlParameter("#ProductId", SqlDbType.Int ) };
using(var cmd1 = new SqlCommand("SELECT ProductName FROM Products WHERE ProductId = #ProductId"))
{
foreach (var parameter in parameters)
{
cmd1.Parameters.Add(parameter);
}
// cmd1.Parameters.Clear(); // uncomment to save your skin!
}
using (var cmd2 = new SqlCommand("SELECT Review FROM ProductReviews WHERE ProductId = #ProductId"))
{
foreach (var parameter in parameters)
{
cmd2.Parameters.Add(parameter);
}
}
}
NOTE: Doing cmd1.Parameters.Clear() just before the last brace of the first using() {} block will save you from the exception (and possible embarrassment).
If you need to reproduce you can use the following scripts to create the objects:
CREATE TABLE Products
(
ProductId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
ProductName nvarchar(32) NOT NULL
)
GO
CREATE TABLE ProductReviews
(
ReviewId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
ProductId int NOT NULL,
Review nvarchar(128) NOT NULL
)
GO
I suspect that SqlParameter "knows" which command it's part of, and that that information isn't cleared when the command is disposed, but is cleared when you call command.Parameters.Clear().
Personally I think I'd avoid reusing the objects in the first place, but it's up to you :)
Adding cmd.Parameters.Clear(); after execution should be fine.
Using blocks do not ensure that an object is "destroyed", simply that the Dispose() method is called. What that actually does is up to the specific implementation and in this case it clearly does not empty the collection. The idea is to ensure that unmanaged resources that would not be cleaned up by the garbage collector are correctly disposed. As the Parameters collection is not an unmanaged resource it is not entirely suprising it is not cleared by the dispose method.
I faced this particular error because I was using the same SqlParameter objects as part of a SqlParameter collection for calling a procedure multiple times. The reason for this error IMHO is that the SqlParameter objects are associated to a particular SqlParameter Collection and you can't use the same SqlParameter objects to create a new SqlParameter collection.
So, instead of this:
var param1 = new SqlParameter{ DbType = DbType.String, ParameterName = param1,Direction = ParameterDirection.Input , Value = "" };
var param2 = new SqlParameter{ DbType = DbType.Int64, ParameterName = param2, Direction = ParameterDirection.Input , Value = 100};
SqlParameter[] sqlParameter1 = new[] { param1, param2 };
ExecuteProc(sp_name, sqlParameter1);
/*ERROR :
SqlParameter[] sqlParameter2 = new[] { param1, param2 };
ExecuteProc(sp_name, sqlParameter2);
*/
Do this:
var param3 = new SqlParameter{ DbType = DbType.String, ParameterName = param1, Direction = ParameterDirection.Input , Value = param1.Value };
var param4 = new SqlParameter{ DbType = DbType.Int64, ParameterName = param2, Direction = ParameterDirection.Input , Value = param2.Value};
SqlParameter[] sqlParameter3 = new[] { param3, param4 };
ExecuteProc(sp_name, sqlParameter3);
using defines a scope, and does the automatic call of Dispose() for which we love it.
A reference falling out of scope will not make the object itself "disappear" if another object has a reference to it, which in this case will be the case for parameters having a reference to cmd1.
I have Also got the same issue Thanks #Jon, based on that I gave example.
When I called the below function in which 2 times same sqlparameter passed. In the first database call, it was called properly, but in the second time, it was give the above error.
public Claim GetClaim(long ClaimId)
{
string command = "SELECT * FROM tblClaim "
+ " WHERE RecordStatus = 1 and ClaimId = #ClaimId and ClientId =#ClientId";
List<SqlParameter> objLSP_Proc = new List<SqlParameter>(){
new SqlParameter("#ClientId", SessionModel.ClientId),
new SqlParameter("#ClaimId", ClaimId)
};
DataTable dt = GetDataTable(command, objLSP_Proc);
if (dt.Rows.Count == 0)
{
return null;
}
List<Claim> list = TableToList(dt);
command = "SELECT * FROM tblClaimAttachment WHERE RecordStatus = 1 and ClaimId = #ClaimId and ClientId =#ClientId";
DataTable dt = GetDataTable(command, objLSP_Proc); //gives error here, after add `sqlComm.Parameters.Clear();` in GetDataTable (below) function, the error resolved.
retClaim.Attachments = new ClaimAttachs().SelectMany(command, objLSP_Proc);
return retClaim;
}
This is the common DAL function
public DataTable GetDataTable(string strSql, List<SqlParameter> parameters)
{
DataTable dt = new DataTable();
try
{
using (SqlConnection connection = this.GetConnection())
{
SqlCommand sqlComm = new SqlCommand(strSql, connection);
if (parameters != null && parameters.Count > 0)
{
sqlComm.Parameters.AddRange(parameters.ToArray());
}
using (SqlDataAdapter da = new SqlDataAdapter())
{
da.SelectCommand = sqlComm;
da.Fill(dt);
}
sqlComm.Parameters.Clear(); //this added and error resolved
}
}
catch (Exception ex)
{
throw;
}
return dt;
}
I encountered this exception because I had failed to instantiate a parameter object. I thought it was complaining about two procedures having parameters with the same name. It was complaining about the same parameter being added twice.
Dim aParm As New SqlParameter()
aParm.ParameterName = "NAR_ID" : aParm.Value = hfCurrentNAR_ID.Value
m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)
aParm = New SqlParameter
Dim tbxDriveFile As TextBox = gvNetworkFileAccess.Rows(index).FindControl("tbxDriveFolderFile")
aParm.ParameterName = "DriveFolderFile" : aParm.Value = tbxDriveFile.Text
m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)
**aParm = New SqlParameter()** <--This line was missing.
Dim aDDL As DropDownList = gvNetworkFileAccess.Rows(index).FindControl("ddlFileAccess")
aParm.ParameterName = "AccessGranted" : aParm.Value = aDDL.Text
**m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)** <-- The error occurred here.
Issue
I was executing a SQL Server stored procedure from C# when I encountered this issue:
Exception message [The SqlParameter is already contained by another SqlParameterCollection.]
Cause
I was passing 3 parameters to my stored procedure. I added the
param = command.CreateParameter();
only once altogether. I should have added this line for each parameter, it means 3 times altogether.
DbCommand command = CreateCommand(ct.SourceServer, ct.SourceInstance, ct.SourceDatabase);
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "[ETL].[pGenerateScriptToCreateIndex]";
DbParameter param = command.CreateParameter();
param.ParameterName = "#IndexTypeID";
param.DbType = DbType.Int16;
param.Value = 1;
command.Parameters.Add(param);
param = command.CreateParameter(); --This is the line I was missing
param.ParameterName = "#SchemaName";
param.DbType = DbType.String;
param.Value = ct.SourceSchema;
command.Parameters.Add(param);
param = command.CreateParameter(); --This is the line I was missing
param.ParameterName = "#TableName";
param.DbType = DbType.String;
param.Value = ct.SourceDataObjectName;
command.Parameters.Add(param);
dt = ExecuteSelectCommand(command);
Solution
Adding the following line of code for each parameter
param = command.CreateParameter();
This is how I have done it!
ILease lease = (ILease)_SqlParameterCollection.InitializeLifetimeService();
if (lease.CurrentState == LeaseState.Initial)
{
lease.InitialLeaseTime = TimeSpan.FromMinutes(5);
lease.SponsorshipTimeout = TimeSpan.FromMinutes(2);
lease.RenewOnCallTime = TimeSpan.FromMinutes(2);
lease.Renew(new TimeSpan(0, 5, 0));
}
If you're using EntityFramework
I also had this same exception. In my case, I was calling SQL via a EntityFramework DBContext. The following is my code, and how I fixed it.
Broken Code
string sql = "UserReport #userID, #startDate, #endDate";
var sqlParams = new Object[]
{
new SqlParameter { ParameterName= "#userID", Value = p.UserID, SqlDbType = SqlDbType.Int, IsNullable = true }
,new SqlParameter { ParameterName= "#startDate", Value = p.StartDate, SqlDbType = SqlDbType.DateTime, IsNullable = true }
,new SqlParameter { ParameterName= "#endDate", Value = p.EndDate, SqlDbType = SqlDbType.DateTime, IsNullable = true }
};
IEnumerable<T> rows = ctx.Database.SqlQuery<T>(sql,parameters);
foreach(var row in rows) {
// do something
}
// the following call to .Count() is what triggers the exception
if (rows.Count() == 0) {
// tell user there are no rows
}
Note: the above call to SqlQuery<T>() actually returns a DbRawSqlQuery<T>, which implements IEnumerable
Why does calling .Count() throw the exception?
I haven't fired up SQL Profiler to confirm, but I suspect that .Count() is triggering another call to SQL Server, and internally it is reusing the same SQLCommand object and trying to re-add the duplicate parameters.
Solution / Working Code
I added a counter inside my foreach, so that I could keep a row count without having to call .Count()
int rowCount = 0;
foreach(var row in rows) {
rowCount++
// do something
}
if (rowCount == 0) {
// tell user there are no rows
}
Afterthough
My project is probably using an old version of EF. The newer version may have fixed this internal bug by clearing the parameters, or disposing of the SqlCommand object.
Or maybe, there are explicit instructions that tell developers not to call .Count() after iterating a DbRawSqlQuery, and I'm coding it wrong.
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 written this function in SQL
alter function TVprest (#emitente int, #mes int, #ano int)
returns float
as
begin
declare #tcu float;
select #tcu = sum(cast(vtprest as money))
from ctrc
where emitente = #emitente and MONTH (EMISSAODATA ) = #mes
and YEAR (EMISSAODATA)=#ano and status = 'A'
if (#tcu is null)
set #tcu = 0;
return #tcu
end
And trying to call the same function in C# with this code:
public double TVprest (int emitente, int mess, int anno)
{
double saida;
SqlConnection abre1 = Tconex.GetConnection();
SqlDataAdapter da3 = new SqlDataAdapter();
if (abre1.State == ConnectionState.Closed) { abre1.Open(); }
SqlParameter emit = new SqlParameter("#emitente", SqlDbType.Int);
emit.Value = emitente;
SqlParameter mes = new SqlParameter("#mes", SqlDbType.Int);
mes.Value = mess;
SqlParameter ano = new SqlParameter("#ano", SqlDbType.Int);
ano.Value = ano;
SqlCommand TotalF = new SqlCommand("SELECT dbo.Tcupom(#emitente,#mes,#ano),", abre1);
TotalF.CommandType = CommandType.Text;
saida = Convert.ToDouble(TotalF.ExecuteScalar());
return saida;
}
When run I get this error :
Failed to convert parameter value from SqlParameter to an Int32
What is wrong? Calling the function with these parameters :
double Tvprest = impx.TVprest(504, 5, 2013);
lblVtprest.Text = Tvprest.ToString();
You haven't added the parameters to your command
SqlCommand TotalF = new SqlCommand("SELECT dbo.Tcupom(#emitente,#mes,#ano),", abre1);
TotalF.Parameters.Add(emit);
TotalF.Parameters.Add(mes);
TotalF.Parameters.Add(ano);
saida = Convert.ToDouble(TotalF.ExecuteScalar());
However, I think you are missing to explain something in your question. You have a function called TVprest but you call a SELECT dbo.Tcupom. Not clear what is that Tcupom
The fundamental error here is, as Steve rightly notes, not adding the parameters correctly. However, as a general code-error-avoidance trick, you might want to try tools like dapper which make it much harder to get it wrong. For example:
return abre1.Query<double>("SELECT dbo.Tcupom(#emitente,#mess,#anno)",
new { emitente, mess, anno }).Single();
That does everything here, but it gets it right, and is easy to read. It even works with your more complex types, i.e.
string region = ...
var customers = connection.Query<Customer>(
"select * from Customers where Region = #region",
new { region }).ToList();
For the past few hours I am trying to do the simplest of the simple things (at least for SQL SERVER) in an Oracle Data Base, through a .NET application using ADO.NET. It seems impossible.
For SQL SERVER I would do this simple task, supposing I have an SqlCommand object
comm.CommandText = #"
DECLARE #next_id INT
SET #next_id = (SELECT ISNULL(MAX(id_col),0) FROM TABLE_1) + 1
INSERT INTO TABLE_1 (id_col, col1, ...) VALUES (#next_id, val1, ...)
SELECT #next_id";
int id = Convert.ToInt32(comm.ExecuteScalar());
That would insert a new record to table TABLE_1 and I would take back the new id in the "id" variable in c# code.
Four simple steps
Declare a variable
Set it to the next available id
Insert the record with the new variable
Return the variable's value
Ok I managed to declare the variable in Oracle query. Also I (think) I managed to give it a value (With SELECT INTO)
How can I get back this variable's value back in c#? How can i SELECT a variable's value to the output stream in Oracle SQL?
I know that there are better ways to achieve getting back an identity column, but that's not the question here. It could be a totally different example. The question is simple.: I have declared a variable inside an oracle sql script that will be executed from within .net app. How can i get the variable's value back to c#, from an oracle query? What is the above code's equivalent with Oracle ADO.NET query?
You'll want to use ODP.NET (Oracle's Oracle Data Access Components):
An example of this is below. Note that in ODP.NET, you can establish a parameters direction (input, inputoutput, output, returnvalue) to correspond with the parameters of the procedure or statement you're running. In this example, I'm grabbing a returnvalue, which is an ID that is generated by the db via a sequence and trigger (its created automagically as far as the .NET app is concerned):
int event_id = 0;
using (OracleConnection oraConn = new OracleConnection(connStr))
{
string cmdText = #"insert into EVENT
(EVENT_NAME, EVENT_DESC)
values
(:EVENT_NAME, :EVENT_DESC)
RETURNING EVENT_ID INTO :EVENT_ID
";
using (OracleCommand cmd = new OracleCommand(cmdText, oraConn))
{
oraConn.Open();
OracleTransaction trans = oraConn.BeginTransaction();
try
{
OracleParameter prm = new OracleParameter();
cmd.BindByName = true;
prm = new OracleParameter("EVENT_NAME", OracleDbType.Varchar2);
prm.Value = "SOME NAME"; cmd.Parameters.Add(prm);
prm = new OracleParameter("EVENT_DESC", OracleDbType.Varchar2);
prm.Value = "SOME DESC"; cmd.Parameters.Add(prm);
prm = new OracleParameter( "EVENT_ID"
, OracleDbType.Int32
, ParameterDirection.ReturnValue);
cmd.Parameters.Add(prm);
cmd.ExecuteNonQuery();
trans.Commit();
// return value
event_id = ConvertFromDB<int>(cmd.Parameters["EVENT_ID"].Value);
}
catch
{
trans.Rollback();
throw;
}
finally
{
trans.Dispose();
}
oraConn.Close();
}
}
The ConvertFromDB is just a generic to cast the return value to its .NET equivalent (an int in this case).
Hope that helps.
EDIT:
You can easily bind an array of values (and retrieve an array of return values) in ODP.NET:
using (OracleConnection oraConn = new OracleConnection(connStr))
{
string cmdText = #"insert into TEST_EVENT
(EVENT_NAME, EVENT_DESC)
values
(:EVENT_NAME, :EVENT_DESC)
RETURNING EVENT_ID INTO :EVENT_ID
";
using (OracleCommand cmd = new OracleCommand(cmdText, oraConn))
{
oraConn.Open();
OracleTransaction trans = oraConn.BeginTransaction();
try
{
string[] event_names = new string[2];
string[] event_descs = new string[2];
int[] event_ids = new int[2];
event_names[0] = "Event1";
event_descs[0] = "Desc1";
event_names[1] = "Event2";
event_descs[1] = "Desc2";
OracleParameter prm = new OracleParameter();
cmd.Parameters.Clear();
cmd.ArrayBindCount = 2;
cmd.BindByName = true;
prm = new OracleParameter("EVENT_NAME", OracleDbType.Varchar2);
prm.Value = event_names; cmd.Parameters.Add(prm);
prm = new OracleParameter("EVENT_DESC", OracleDbType.Varchar2);
prm.Value = event_descs; cmd.Parameters.Add(prm);
prm = new OracleParameter( "EVENT_ID"
, OracleDbType.Int32
, ParameterDirection.ReturnValue);
cmd.Parameters.Add(prm);
cmd.ExecuteNonQuery();
trans.Commit();
// get return values
event_ids = (int[])(cmd.Parameters["EVENT_ID"].Value);
}
catch
{
trans.Rollback();
throw;
}
finally
{
trans.Dispose();
}
oraConn.Close();
}
}