Option Strict On and DBNull.Value - vb.net

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.

Related

Implicit conversion from object to integer and other errors

I have two Layes Classes Business Layer And Data Layer And i have The Main class i called DatabaseManager contain all functions i need for stored procedures
I search on these errors I cannot find solutions
First Error in DatabaseManager class is :
implicit conversion from object to integer
Public Function ExecuteScalar(ByVal sql As String) As Object
Dim cmd As New SqlCommand(sql) With {
.CommandType = CommandType.Text,
.Connection = Connection
}
Dim retval As Integer = ExecuteScalar(cmd)
Return retval
End Function
In Data Layer Class i have this code :
Friend Function Get_Last_Visits_Type(ByRef cmd As SqlCommand)
Dim retval As Integer
cmd = New SqlCommand("Get_Last_Visits_Type")
retval = dm.ExecuteScalar(cmd)
Return retval
End Function
I got two errors here
function without an 'as' clause return type of object assumed
And
implicit conversion from object to integer
When Form Loaded i put this code on Load action :
TxtVisitTypeID.Text = Val(p.Get_Last_Visits_Type)
And i got this error :
implicit conversion from Double to String
Thanks...
Quite a few problems here as mentioned in comments:
Avoid naming a function anything that is a reserved word in the scope of your project at the very least: ExecuteScalar is a method of SqlCommand so use something like MyExecuteScalar instead.
Dim retval As Integer = ExecuteScalar(cmd) probably should be Dim retval As Integer = cmd.ExecuteScalar() unless you want a recursion (which I doubt). (Refer 1.)
Turn Option Strict on in your project settings. As mentioned, ALWAYS have this on. (And I prefer to have Option Explicit on and Option Infer off as well for similar reasons.)
With compile options set as in 3. you will have (valid) compilation errors pertaining to type conversion (at least), with a good chance of resulting in working code once you fix them. Eg Dim retval As Integer = Ctype(cmd.ExecuteScalar(), Integer) (if you're sure that the result of the query will be Integer, otherwise you will need to test and/or error trap).
Connection isn't defined anywhere: .Connection = Connection. You don't pass it nor declare it.
Since retval is declared as an Integer then the return type can also be tightened up to Integer as well, rather than Object.
Your second function has no return type.
What is dm? Not declared/defined.
Consider using Using blocks to close-and-dispose of SQL connection and command on exit.
CommandType.Text is the default so you only need to state it by way of explanation.
Here's what I'd do with your first function:
Public Function MyExecuteScalar(ByVal sql As String) As Integer
Try
Using con As New SqlConnection(sql)
Using cmd As New SqlCommand(sql, con)
Return CType(cmd.ExecuteScalar(), Integer)
End Using
End Using
Catch
Return -1 ' Or something recognizable as invalid, or simply Throw
End Try
End Function
Addressing the first function:
This code is too abstract to be useful. The name of the function is bad. It appears to be recursive but the value you pass back to the function is not a string but a command. If the line of code is Dim retval As Integer = cmd.ExecuteScalar(), then .ExecuteScalar() returns an Object. You cannot convert an Object to an Integer without a conversion method. If you are declaring retval as an Integer why would you have typed your function As Object? I won't even get into the connection problem here. I suggest you delete the function and start again.
Addressing the second function:
Why are you passing the command ByRef? This function has no connection at all! How do you expect it to execute anything? Same problem with retval As Integer and ExecuteScalar returning an Object.
Again, delete and start again.
Now to the code in Form.Load:
Val went out with VB6. I can give unanticipated results. Guess what, Val returns a Double. A .Text property expects a string. Also you appear to be calling the function you showed us above. That function asks for the calling code to provide an argument, namely an SqlCommand object.
My suggestions:
Forget about layers and try to learn the basics of data access with ADO.net. Turn on Option Strict now and forever. Ask new questions with a single problem. Tell us what line the error occurs on. You have been advised before that functions require a datatype but it doesn't seem to sink in.

VB.NET Nothing evaluating to False [duplicate]

Coming from Basic boolean logic in C#, I was wondering why:
Dim b As Boolean
Dim obj As Object = Nothing
'followig evaluates to False'
b = DirectCast(Nothing, Boolean)
'This throws an "Object reference not set to an instance of an object"-Exception'
b = DirectCast(obj, Boolean)
A CType(obj, Boolean) would evaluate to False(just as CBool(obj)). I think it is because the compiler uses a helper function, but that is not my theme.
Why does casting Nothing to Boolean evaluates to False, whereas casting an object that is Nothing to Boolean throws an Nullreference-Exception? Does that make sense?
[Option Strict ON]
Presumably, this is because Nothing in VB.NET is not exactly the same thing as null in C#.
In the case of value types, Nothing implies the default value of that type. In the case of a Boolean, the default value is False, so the cast succeeds.
One of the primary differences between value types such as Integer or structures and reference types such as Form or String is that reference types support a null value. That is to say, a reference type variable can contain the value Nothing, which means that the variable doesn't actually refer to a value. In contrast, a value type variable always contains a value. An Integer variable always contains a number, even if that number is zero. If you assign the value Nothing to a value type variable, the value type variable just gets assigned its default value (in the case of Integer, that default value is zero). There is no way in the current CLR to look at an Integer variable and determine whether it has never been assigned a value - the fact that it contains zero doesn't necessarily mean that it hasn't been assigned a value.
–The Truth about Nullable Types and VB...
EDIT: For further clarification, the reason the second example throws a NullReferenceException at run-time is because the CLR is attempting to unbox the Object (a reference type) to a Boolean. This fails, of course, because the object was initialized with a null reference (setting it equal to Nothing):
Dim obj As Object = Nothing
Remember that, as I explained above, the VB.NET keyword Nothing still works the same way as null in C# when it comes to reference types. That explains why you're getting a NullReferenceException because the object you're attempting to cast is literally a null reference. It does not contain a value at all, and therefore cannot be unboxed to a Boolean type.
You don't see the same behavior when you try to cast the keyword Nothing to a Boolean, i.e.:
Dim b As Boolean = DirectCast(Nothing, Boolean)
because the keyword Nothing (this time, in the case of value types) simply means "the default value of this type". In the case of a Boolean, that's False, so the cast is logical and straightforward.
There's a couple of things you have to realize here.
The first is what others have already pointed out: Nothing can be interpreted by the VB compiler as simply the Boolean value False given the proper context, such as Dim b As Boolean = Nothing.
This means that when the compiler sees this:
b = DirectCast(Nothing, Boolean)
It sees a literal (Nothing) and also sees that you want to use this literal as a Boolean. That makes it a no-brainer.
But now here's the second thing you have to realize. DirectCast on an Object is essentially an unboxing operation (for value types). So what needs to happen from the VB compiler's perspective is: there needs to be a Boolean in that box, or else the operation will fail. Since there is in fact nothing in the box—and this time we're really talking nothing, as in null—it throws an exception.
If I were to translate this code to C#, it would look like this:
bool b;
object obj = null;
b = (bool)default(bool);
b = (bool)obj;
Hopefully that makes things a bit clearer?
There's a difference between using the keyword (literal) Nothing, and using a reference variable whose value is Nothing.
In VB.NET, the literal (keyword) Nothing gets special treatment. The Nothing keyword can be automatically converted into a value type, using the default value of that type.
A reference variable whose value is Nothing is different. You don't get the special behaviour.
The documentation says DirectCast "requires an inheritance or implementation relationship between the data types of the two arguments".
Clearly Object does not inherit from or implement Boolean, unless you have put a boxed Boolean into an object variable.
So the code below fails at runtime with an exception.
Dim obj As Object = Nothing
b = DirectCast(obj, Boolean)
To get the expected behavior, you need this code:
Dim b As Boolean?
Dim obj As Object = Nothing
b = DirectCast(obj, Boolean?)
The character ? mean Nullable(of ).
I'm finding that comparing of the Boolean variable to a string of "True", "False" or Is nothing seems to ensure that I get the correct comparisons. I was using a function to return an html string of a div with an image of a checked or unchecked radio button and was having the issue of nothing coming back as false. Using the variable = "True" or "False" string and doing the last check with IS NOTHING helped to resolve that issue.
Dim b as boolean = nothing
response.write CheckValue(b = "True")
response.write (b = "False")
response.write (b is nothing)
Function CheckValue(inVal as boolean) as string
if inVal then
return ("<div><img src="checked.png" ></div>
else
return ("<div><img src="unchecked.png" ></div>
end if
end function
The system seems to do the conversion to string when implicitly compared to a string whereas using the .tostring method just creates an error while allowing the last comparison to actually compare to a value of nothing.
Hopefully that helps somewhat. It at least let me

"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.

ODP.NET OracleCommand optional parameter never getting default value

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

VB.NET generic function for checking for a value of null

I am trying to write a generic function that will check each database parameter to see if it is null, and if so, return DBNull; if not, return the object.
So here is my function:
Public Shared Function CheckForNull(ByVal obj As Object) As Object
If obj <> Nothing Then Return obj Else Return DBNull.Value
End Function
My problem is that some of the objects I am passing to the function are Nullable. So I may pass the function a Long? or Int?, but when a nullable type is passed to the function, it is converted to its value type. So if I pass a Long? that has a value of 0, the function returns DBNull because the Long? is converted to Long and a value of 0 for a Long is equivalent to Nothing. Is there anyway to make this function work for Nullable types as well?
If not, I will just fall back to using the following statements instead of one generic funciton call:
IIf(nullableVar.HasValue, nullableVar, DBNull.Value))
and
IIf(nonNullableVar IsNot Nothing , nonNullableVar, DBNull.Value))
That is not why your logic is failing. When evaluating an expression like (x=y) or (x<>y), if either argument is Nothing, VB.Net returns FALSE. Your conditional is always falling through to the ELSE clause. So instead of:
if obj <> Nothing then
try this instead:
if obj isnot nothing then