ODP.NET OracleCommand optional parameter never getting default value - vb.net

When I'm calling a function from a PL/SQL package, there is a IN optional parameter (Date) with default value of SYSDATE.
When I execute my commmand, I don't have a choice but to add the parameter. Even if there is no value set, the function always receive null as the entered value, so it never affects the default value.
Dim cmd As New OracleCommand(con)
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "PACK.RefreshData"
Dim param1 As New OracleParameter()
param1.Direction = ParameterDirection.Input
param1.ParameterName = "p_date"
param1.DbType = DbType.Date
cmd.Parameters.Add(param1)
param1 = New OracleParameter()
param1.Direction = ParameterDirection.Output
param1.ParameterName = "po_errorCode"
param1.DbType = DbType.String
cmd.Parameters.Add(param1)
param1 = New OracleParameter()
param1.Direction = ParameterDirection.Output
param1.ParameterName = "po_errorDescription"
param1.DbType = DbType.String
cmd.Parameters.Add(param1)
con.Open()
cmd.ExecuteNonQuery()
In another question, somebody said the command property BindByName set to true works only for regular queries, not storedProcs. So what can I do to call the function without passing a value with my IN parameter?
Here is the Stored Proc header
Procedure RefreshData
(
p_date IN DATE := SYSDATE,
po_errorCode OUT varchar2,
po_errorDescription OUT varchar2
);
N.B. : I'm using Oracle.DataAccess.dll version 4.112.4.0
UPDATE : Here is the explanation taken from the oracle thread, from my answer below :
I think what it boils down to is that DervieParameters is deriving parameters for all of the proc params, even though they have default values, and that seems to me to be the correct behavior. Once you have parameters in the collection, if you don't assign a value to them, null will be passed.
What you probably need to do is
1) the best option, is not to use deriveparameters in the first place, and manually build the parameters collection, adding parameters for only the things you don't want to have default values:
DeriveParameters incurs a database round-trip and should only be used during design time. To avoid unnecessary database round-trips in a production environment, the DeriveParameters method itself should be replaced with the explicit parameter settings that were returned by the DeriveParameters method at design time.
2) If you want to continue using DeriveParameters, remove the unwanted parameters from the OracleParameters collection.

If you have default-value parameter not at the tail of the parameters chain you should perform named parameters call to avoid passing this default-value parameter. I think your data provider doesn't support this call type, so you should wrap your call in anonymous pl\sql block like this :
begin
PACK.RefreshData(po_errorCode => :po_errorCode, po_errorDescription=>:po_errorDescription);
end;
and then do what did before in VB code (ofcourse you CommandType will be Text).
Another solution is to move your default-value parameter to the end of parameters chain like this:
Procedure RefreshData
(
po_errorCode OUT varchar2,
po_errorDescription OUT varchar2,
p_date IN DATE := SYSDATE
);

Finally I have been able to find a way to call the procedure with this thread :
https://community.oracle.com/thread/2248928?tstart=0
Here is my corrected code :
con.Open()
cmd = con.CreateCommand()
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "Pack.RefreshData"
cmd.BindByName = True
OracleCommandBuilder.DeriveParameters(cmd)
cmd.Parameters.RemoveAt(0) 'I remove the optional parameter
cmd.ExecuteNonQuery()
Then I retreive my OUT parameters
Dim outPrm1 as String = ""
If cmd.Parameters("po_errorDescription").Value IsNot Nothing Then
outPrm1 = cmd.Parameters("po_errorDescription").Value
End If

Related

Option Strict On and DBNull.Value

I've started converting an application of mine to use Option Strict On. I've been doing the CStr,Ctype etc, and it's been going well.
SQLCommand.Parameters.AddWithValue("#TERMINATE", If(IsNothing(txtEarningsTerminated.DateValue), DBNull.Value, txtEarningsTerminated.DateValue))
Then I hit this. The txtEarningsTerminated.DateValue is a custom masked text box. When it's value is Nothing I don't want to store anything in the database. However, it states
Cannot infer a common type, and Option Strict On does not allow 'Object' to be assumed.
When I change DBNull.Value to "" or nothing, the error goes away. However as Nothing, it fails during runtime stating
The parameterized query '(#CONTROL int,#CLIENTCODE nvarchar(10),#NoBill int,#TERMINATE nv' expects the parameter '#TERMINATE', which was not supplied.
I want to put a NULL in the database. This value can be a date and then become a NULL.
How do I translate this so as to not produce an error under Option Strict On?
The reason is because operator If(condition, executeAndReturnIfTrue, executeAndReturnIfFalse) expects that both true and false expressions will return same type.
In your case you return DbNull type if true and String(or some other type you didn't mentioned) if result is false.
If create SqlParameter more explicitly, then you can use next approach:
Dim value As Object = Nothing
If txtEarningsTerminated.DateValue Is Nothing Then
value = DbNull.Value
Else
value = xtEarningsTerminated.DateValue
End If
Dim param As SqlParameter = New SqlParameter With
{
.ParameterName = "#TERMINATE",
.SqlDbType = SqlDbType.VarChar, 'Or use your correct type
.Value = value
}
As mentioned in the comments using AddWithValue will force ADO.NET to decide the sql type automatically for your value, which can lead in different problems. For example everytime you run same query with different values your query plan will be recompiled every time you change value.
You can create extension method for string
Public Module ExtensionsModule
Public Function DbNullIfNothing(this As String) As Object
If this Is Nothing Then Return DBNull.Value
Return this
End Function
End Module
Then use it instead of your "inline If method"
Dim value As String = Nothing
Dim param As SqlParameter = New SqlParameter With
{
.ParameterName = "#TERMINATE",
.SqlDbType = SqlDbType.VarChar,
.Value = value.DbNullIfNothing()
}
So the answer was obvious and in the error. I just had to encase the DBNull.Value in a Ctype Object
CType(DBNull.Value, Object)
And then the code would compile just fine.
Here's one way to do it (assuming you're using stored procedures in your database):
In the stored procedure you're calling, set the date input parameter to equal null, making it an optional field with a default value of null (#terminate DATETIME = NULL).
That way, if you don't include that parameter in your procedure call, the procedure won't fail.
Set the default value of the date field to DateTime.MinValue so it always has some value. Then you can test to see if it is equal to a date other than DateTime.MinValue. If it is a valid date value, add the line to include the date parameter to the procedure call. If it is equal to DateTime.MinValue, don't add the parameter to the call.

"Procedure or function expects parameter which was not supplied"

I've been trying to figure out this bug for a while now, some help would be appreciated. Thanks.
Here is my error message:
Procedure or function 'getAvailableSMSNumbers' expects parameter '#Election_ID', which was not supplied.
Here is my sql code:
CREATE PROCEDURE {databaseOwner}{objectQualifier}getAvailableSMSNumbers
#Election_ID nvarchar(20)
AS
SELECT *
FROM {databaseOwner}{objectQualifier}icc_sms_phones
LEFT JOIN {databaseOwner}{objectQualifier}icc_sms_elections ON sms_elections_sms_number = phones_number
WHERE sms_elections_sms_number IS NULL
OR sms_elections_id = #Election_ID
GO
Function:
Public Overrides Function getAvailableSMSNumbers(eventid As String) As IDataReader
Dim dtable As New DataTable
Using sqlconn As New SqlConnection(Me.ConnectionString)
Using sqlcomm As New SqlCommand
Using sqlda As New SqlDataAdapter
sqlcomm.Connection = sqlconn
sqlcomm.CommandType = CommandType.StoredProcedure sqlcomm.CommandText=GetFullyQualifiedName("getAvailableSMSNumbers")
sqlcomm.Parameters.AddWithValue("#Election_ID", eventid)
sqlda.SelectCommand = sqlcomm
sqlconn.Open()
sqlda.Fill(dtable)
sqlconn.Close()
Return dtable.CreateDataReader
End Using
End Using
End Using
End Function
Where the function is used:
Public Function getAvailableSMSNumbers(eventid As String) As List(Of phoneModel)
Dim numbers As New List(Of phoneModel)
Dim number As phoneModel
numbers = CBO.FillCollection(Of phoneModel)(dal.getAvailableSMSNumbers(eventid))
For Each number In numbers 'dal.getAvailableSMSNumbers(eventid).Rows
number = New phoneModel
With number
.val = ("PHONES_NUMBER").ToString
.text = String.Format("{0:# (###) ###-####}", Long.Parse(.val))
End With
numbers.Add(number)
Next
Return numbers
End Function
If you need anymore information, let me know, and I will add it.
This typically occurs if the object supplied as the value of your SQL parameter is NULL, but the stored procedure does not allow null values (which yours does not). You can set a conditional breakpoint on this line sqlcomm.Parameters.AddWithValue("#Election_ID", eventid) to make sure the eventid parameter is not null.
It might also be a good idea to use defensive coding, and in your getAvailableSMSNumbers function, check to make sure eventid is not null, and if it is, throw an exception or provide some type of feedback for the user.
As an option you can try to re-compile your stored procedure to allow NULL parameter :
CREATE PROCEDURE {databaseOwner}{objectQualifier}getAvailableSMSNumbers
#Election_ID nvarchar(20) = NULL
AS
That means that the default value of your Parameter will be null in case there is no value on input. This solution will be nice in case you want to return empty datatable without error. In any other case you have to debug your VB code and understand where the issue starts.
Think about how you are calling you procedure. When you call you need to supply the value of the procedure: For example,
Call get_particular_girl_from_girlsTable("Jane")
where get_particular_girl_from_girlsTable is the procedure and "Jane" is value for parameter GirlName.
Did you verify if
cmd.CommandType = CommandType.**StoredProcedure**
By default, the value is Text, expecting a SELECT, INSERT or other command text.

Oracle function is not a procedure or is undefined. Statement ignored

I am trying to call an Oracle function from C# that returns multiple rows but it is not working. Here is the function I am using:
create or replace function return_columns(
tableName IN varchar
)
return types.ref_c
as
c_result types.ref_c;
begin
open c_result for
select column_name
from all_tab_columns
where table_name = tableName;
return c_result;
end return_columns;
Here is the type:
create or replace package types
as
type ref_c is ref cursor;
end;
I am in C# code calling the function like this:
OracleConnection oraConn = new OracleConnection("DATA SOURCE=MySource;PASSWORD=MyPassword;USER ID=MyID");
OracleCommand objCmd = new OracleCommand("MyID.RETURN_COLUMNS", oraConn);
objCmd.CommandType = CommandType.StoredProcedure;
OracleParameter oraParam = new OracleParameter("tableName", OracleType.VarChar);
oraParam.Value = "MY_TABLE";
oraCmd.Parameters.Add(oraParam);
oraConn .Open();
DataTable dt = new DataTable();
OracleDataAdapter ad = new OracleDataAdapter(objCmd);
ad.Fill(dt);
oraConn.Close();
And it keeps returning this error:
'RETURN_COLUMNS' is not a procedure or is undefined ORA-06550: line 1, column 7: PL/SQL: Statement ignored
What is wrong with my Oracle function?
You simply need to define one more parameter, a parameter responsible for return value. Here is an example:
OracleParameter retVal = new OracleParameter("retVal", OracleDbType.RefCursor);
retVal.Direction = ParameterDirection.ReturnValue;
Note #1: The retVal parameter should be added first in the parameter list, otherwise you might receive ORA-00306: wrong number or type of arguments.. error.
cmd.Parameters.Add(retVal); -- ReturnValue parameter is being added first
cmd.Parameters.Add(tabName); -- then goes everything else
Note #2: It would be better to use ODP for .NET instead of obsolete and deprecated Microsoft Oracle client (System.Data.OracleClient)
Note #3: Use varchar2 data type instead of varchar in your PL/SQL code. As of now they are synonyms but their behavior might change in the future.
It's been a while, but this should get you moving in the right direction...
OracleParameter prm = cmd.CreateParameter();
prm.OracleDbType = OracleDbType.RefCursor;
prm.ParameterName = "retcurse";
prm.Direction = ParameterDirection.ReturnValue;
oraCmd.Parameters.Add(prm);
OracleRefCursor rc = (OracleRefCursor)prm.Value;
// Not sure about this next part off the top of my head...
OracleDataAdapter ad = new OracleDataAdapter(objCmd);
DataSet ds = new DataSet();
ad.Fill(ds, "retcurse", rc);
// May also be this
// ad.Fill(ds, "retcurse", (OracleRefCursor)(oraCmd.Parameters["retcurse"].Value));

Why doesn't my VB.Net code see the value that my SPROC is returning?

I've written some logic into a sproc that returns either 1 or 0 for true/false. Works great if I run it manually, but if I call it from VB I always get 0/false, no matter what the actual value the sproc returns. Probably missing something silly, but I'm just not seeing it. The code is partially copied from other code in our system that does work properly, but uses a dynamically constructed sproc call. I'm attempting to modify that code to use parameterized sproc calls.
The sproc returns the value in the column named "Allowed".
The VB code (copied and sanitized for variable names/content) looks like:
Public Function sample(ByVal parm1 As String, ByVal parm2 As String) As Boolean
Dim Allowed As Boolean = False
Try
Dim MyConnection As New SqlConnection(ConnString)
Dim MyDataAdapter As New SqlDataAdapter("sproc", MyConnection)
Dim DS As New DataSet
MyDataAdapter.SelectCommand.CommandType = CommandType.StoredProcedure
MyDataAdapter.SelectCommand.Parameters.Add(New SqlParameter("#parm1", SqlDbType.Char, 14))
MyDataAdapter.SelectCommand.Parameters("#parm1").Value = parm1
MyDataAdapter.SelectCommand.Parameters.Add(New SqlParameter("#parm2", SqlDbType.Char, 30))
MyDataAdapter.SelectCommand.Parameters("#parm2").Value = parm2.ToUpper()
MyDataAdapter.Fill(DS)
If DS.Tables.Count > 0 Then
If DS.Tables(0).Rows.Count > 0 Then
Allowed = Convert.ToBoolean(DS.Tables(0).Rows(0).Item("Allowed"))
End If
End If
Catch ex As Exception
' Real code has error handling here that's not getting hit
End Try
Return Allowed
End Function
if you are only returning a true/false you should use execute scalar. there is a good example here
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.executescalar.aspx?cs-save-lang=1&cs-lang=vb#code-snippet-3
I would make sure to cast your value in the sproc as a bit something like:
select cast(1 as bit)
Select 1 and select Cast(1 as bit) will be two different things.

How do you use ExecuteScalar to return a single value from an Oracle Database?

Been using the code below to return a single record from the database. I have read that ExecuteScalar is the right way to return a single record. I have never been able to get ExecuteScalar to work though. How would I change this to return a single value in VB.Net using ExecuteScalar?
Dim oracleConnection As New OracleConnection
oracleConnection.ConnectionString = LocalConnectionString()
Dim cmd As New OracleCommand()
Dim o racleDataAdapter As New OracleClient.OracleDataAdapter
cmd.Connection = oracleConnection
cmd.CommandText = "FALCON.CMS_DATA.GET_MAX_CMS_TH"
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.Add(New OracleParameter("i_FACID_C", OracleType.Char)).Value = facilityShortName
cmd.Parameters.Add(New OracleParameter("RS_MAX", OracleType.Cursor)).Direction = ParameterDirection.Output
Try
Using oracleConnection
oracleConnection.Open()
Using oracleDataAdapter
oracleDataAdapter = New OracleClient.OracleDataAdapter(cmd)
Dim workingDataSet As DataSet
oracleDataAdapter.TableMappings.Add("OutputSH", "RS_MAX")
workingDataSet = New DataSet
oracleDataAdapter.Fill(workingDataSet)
For Each row As DataRow In workingDataSet.Tables(0).Rows
Return CDate(row("MAXDATE"))
Next
End Using
End Using
From Microsoft
"The ExecuteOracleScalar() method of the OracleCommand class is used to execute a SQL statement or stored procedure that returns a single value as an OracleType data type. If the command returns a result set, the method returns the value of the first column of the first row. The method returns a null reference if a REF CURSOR is returned rather than the value of the first column of the first row to which the REF CURSOR points. The ExecuteScalar() method of the OracleCommand class is similar to the ExecuteOracleScalar() method, except it returns a value as a .NET Framework data type.
Having said that, neither of these methods is useful when working with Oracle stored procedures. Oracle stored procedures cannot return a value as part of the RETURN statement, only as OUT parameters—see the Stored Procedures That Do Not Return Data section. Also, you cannot return a result set except through a REF CURSOR output parameter—this is discussed in the next section.
You can retrieve the return value for an Oracle function only by using a RETURN parameter (shown in the previous section) and not by using the one of the ExecuteScalar methods."
http://msdn.microsoft.com/en-us/library/ms971506.aspx
ExecuteScalar returns a single value (scalar) not a record.
not sure why the other answer is marked as accepted, as it doesn't appear to answer your question
How would I change this to return a single value in VB.Net using ExecuteScaler
ExecuteScalar will only return a Single Value - so keep that in mind when writing the query portion of your command.
The code to accomplish this would be as follows:
oracleConnection.Open
Dim obj as object 'Object to hold our return value
obj = cmd.ExecuteScalar()
oracleConnection.Close
If obj IsNot Nothing then
Return CDate(obj)
end if