Checking if OracleParameter.Value is Null without doing ToString conversion - vb.net

I have a stored procedure which has some OUT parameters. On the .NET side I'm perplexed about how to check if the parameter is null before converting it to a .NET Double?. I've done this sort of thing a lot before across several different projects and I've never encountered this issue before...
This is every combination I've tried:
Command.Parameters.Add("PparStsValue", OracleDbType.Decimal).Direction = ParameterDirection.Output
PparStsValue = If(IsDBNull(Command.Parameters("PparStsValue").Value) OrElse
IsNothing(Command.Parameters("PparStsValue").Value) OrElse
Command.Parameters("PparStsValue").Value.ToString = "",
Nothing,
CDbl(Command.Parameters("PparStsValue").Value.ToString))
There is one other thing I've done which does work, but it just seems ridiculous which is to check:
Command.Parameters("PparStsValue").Value.ToString = "null"
This evaluates to True and therefore I can identify nulls... But why does a null value convert to the string "null"? That seems so inconsistent with everything else which usually converts to "".
That aside, the main question is why don't either IsDBNull and IsNothing return true?

When using ODP your parameter does not automatically return CLR type but rather oracle type. So the way to do it for numeric OracleParameter is this
Dim oraDec as OracleDecimal =
DirectCast(Command.Parameters("PparStsValue").Value, OracleDecimal)
Dim dbl as Double
If Not oraDec.IsNull Then
dbl = Convert.ToDouble(oraDec.Value)
End If

Related

Why is the 2nd term in this If-AndAlso statement evaluated early?

Edit for clarity: This is a pretty odd behaviour from the compiler, I'm asking for why it behaves this way in general, rather than how to work around it (there are several simple solutions already).
Recently, I came across a piece of code which throws contains a subtle mistake, and ends up throwing an exception. A shortened, contrived example:
Dim list As List(Of Integer) = Nothing
If list?.Any() AndAlso list.First() = 123 Then
MessageBox.Show("OK then!")
End If
In the real example, list was only occasionally Nothing, I'm just shortening the code for clarity. The intention with the first term in the If statement was to both check that list is not Nothing, and to also test for the existence of at least one element. Since in this case, list is Nothing, the list?.Any() actually / typically evaluates to Nothing. Somewhat counter-intuitively, the 2nd term, namely list.First() = 123 is also evaluated by the runtime, causing an obvious exception. This is somewhat counter-intuitive, since at first guess most people would imagine that Nothing is seem as False, and since we're using an AndAlso here, the short-circuit operator would prevent the 2nd half of the If statement from executing.
Additional investigation / "What have you tried:"
Quick check to confirm that a shortened If list?.Any() Then seems to treat list?.Any() as a False:
Dim list As List(Of Integer) = Nothing
If list?.Any() Then
MessageBox.Show("OK then!") 'This line doesn't get hit / run
End If
Also, we can work around the issue by in several ways: If list IsNot Nothing AndAlso list.Any() AndAlso list.First() = 123 Then would work just fine, as would If If(list?.Any(), False) AndAlso list.First() = 123 Then.
Since VB.Net is not my usual language, I thought I'd have a look at this in C#:
List<int> list = null;
if (list?.Any() && list.First() == 123)
{
MessageBox.Show("OK then!");
}
However, this gives a compilation error:
error CS0019: Operator '&&' cannot be applied to operands of type 'bool?' and 'bool'
Apart from the obvious fact that the stricter compiler check would prevent this mistake from being made in the C# scenario, this leads me to believe that type coercion is happening in the VB.Net scenario. One guess might be that the compiler is trying to cast the Boolean result of the 2nd term to a nullable Boolean, however this doesn't make a whole lot of sense to me. Specifically, why would it evaluate it prior to / same time as the left side, and abandoning the entire process early, like it should? Looking back at the VB.Net examples that do work correctly, all involve explicit checks which have a simple Boolean result, rather than a nullable Boolean.
My hope is that someone can give some good insight into this behaviour!
This appears to be an omission(bug) in the VB compiler's syntax evaluation. The documentation for the ?. and ?() null-conditional operators (Visual Basic) states:
Tests the value of the left-hand operand for null (Nothing) before
performing a member access (?.) or index (?()) operation; returns
Nothing if the left-hand operand evaluates to Nothing. Note that, in
the expressions that would ordinarily return value types, the
null-conditional operator returns a Nullable.
The expression list?.Any() (Enumerable.Any Method) would ordinarily return a Boolean (a ValueType), so we should expect list?.Any() to yield a Nullable(Of Boolean).
We should see a compiler error as a Nullable(Of Boolean) can not participate in an AndAlso Operator expression.
Interestingly, if we treat list?.Any() as a Nullable(Of Boolean), it is seen as documented.
If (list?.Any()).HasValue() AndAlso list.First = 123 Then
' something
End If
Edit: The above does not really address your why?.
If you de-compile the generated IL, you get something like this:
Dim source As List(Of Integer) = Nothing
Dim nullable As Boolean?
Dim nullable2 As Boolean? = nullable = If((Not source Is Nothing), New Boolean?(Enumerable.Any(Of Integer)(source)), Nothing)
nullable = If((nullable2.HasValue AndAlso Not nullable.GetValueOrDefault), False, If((Enumerable.First(Of Integer)(source) Is &H7B), nullable, False))
If nullable.GetValueOrDefault Then
MessageBox.Show("OK then!")
End If
This obviously will will not compile, but if we clean it up a bit, the source of the issue becomes apparent.
Dim list As List(Of Integer) = Nothing
Dim nullable As Boolean?
Dim nullable2 As Boolean? = If(list IsNot Nothing,
New Boolean?(Enumerable.Any(Of Integer)(list)),
Nothing)
' nullable2 is nothing, so the 3rd line below is executed and throws the NRE
nullable = If((nullable2.HasValue AndAlso Not nullable.GetValueOrDefault),
False,
If((Enumerable.First(Of Integer)(list) = 123), nullable, False))
If nullable.GetValueOrDefault Then
MessageBox.Show("OK then!")
End If
Edit2:
The OP has found the following statement from the documentation for Nullable Value Types (Visual Basic)
AndAlso and OrElse, which use short-circuit evaluation, must evaluate
their second operands when the first evaluates to Nothing.
This statement makes sense if Option Strict Off is in force and using the OrElse Operator as Nothing can implicitly be converted to False. For the OrElse operator, the second expression is not evaluated only if the first expression is True. In the case of the AndAlso operator, the second operator is not evaluated if the first expression is True.
Also, consider the following code snippet with Option Strict On.
Dim list As List(Of Integer) = Nothing
Dim booleanNullable As Nullable(Of Boolean) = list?.Any()
Dim b As Boolean = (booleanNullable AndAlso list.First() = 123)
If b Then
' do something
End If
This re-arrangement of the original logic does yield a compiler error.
With Option Strict Off, no compiler error is generated, yet the same run-time error occurs.
My Conclusion: As originally stated, this is a bug. When an AndAlso operator is included in a If-Then block the compiler treats the result of the null conditional operator using Option Strict Off type conversion relaxation regardless of the actual state of Option Strict.
Due in part to its heritage of strong support for database access, classic VB supported Null values as a first-class part of the type system. Null is included in the truth tables for all of the built-in logical operators (for example, see https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/imp-operator ).
For this reason, it is not surprising that (as you later noted) VB would choose to use similar semantics to a classic Null for a Boolean? which does not have a value.
As a practical matter, the best way to address this is probably the following:
If (list?.Any()).GetValueOrDefault() AndAlso list.First() = 123 Then
In the alternative, you could use FirstOrDefault to avoid needing the check Any first, although this does rely on the value you seek not being the default (or using a "magic" replacement value, remember that you can override the default in GetValueOrDefault), e.g.
If list?.FirstOrDefault().GetValueOrDefault() = 123 Then
Please try
if (list?.Any() IsNot Nothing AndAlso list.First() == 123)

Strange behaviour of the If() statement

today I stumbled upon a strange behaviour of the VB.net If() statement. Maybe you can explain why it is working like it does, or maybe you can confirm that it is a bug.
So, I have a SQL database with a table "TestTable" with an int column "NullableColumn" that can contain NULL. I'd like to read out the content of this column.
So I declare a variable of type Nullable(Of Integer) for that matter, open a SqlClient.SqlDataReader for "SELECT NullableColumn FROM TestTable" and use the following code to get the content of this column:
Dim content as Nullable(Of Integer)
...
Using reader as SqlClient.SqlDataReader = ...
content = If(reader.IsDBNull(reader.GetOrdinal("NullableColumn")), Nothing, reader.GetInt32(reader.GetOrdinal("NullableColumn")))
End Using
But after that my variable content has the value 0, not Nothing as I would have expected.
When debugging everything looks alright, so
reader.GetOrdinal("NullableColumn") delivers the correct ordinal position of this column (which is 0)
reader.IsDBNull(0) and reader.IsDBNull(reader.GetOrdinal("NullableColumn")) deliver True, since the content of this column indeed is NULL
If(1=2, Nothing, "Not Nothing") delivers the String "Not Nothing"
If(1=1, Nothing, "Not Nothing") delivers Nothing
reader.GetInt32(reader.GetOrdinal("NullableColumn")) throws an error, since NULL can't be converted to Integer
So, why does my variable has the value 0?
In VB Nothing is not the same as null. The If operator must determine the type of its result based on the arguments passed to it. Nothing, of course, has no type so the only type that the If operator can return in your code is Int32. If the IsDBNull method returns true, then the If operator returns Nothing cast as Int32. In VB, Nothing returns the default value for a type. For an Int32, the default value is 0.
From MSDN on the Nothing keyword:
Nothing represents the default value of a data type. The default value depends
on whether the variable is of a value type or of a reference type.
For non-nullable value types, Nothing in Visual Basic differs from null in C#.
In Visual Basic, if you set a variable of a non-nullable value type to Nothing,
the variable is set to the default value for its declared type. In C#, if you
assign a variable of a non-nullable value type to null, a compile-time error
occurs.
I think just a regular If would work best:
If Not reader.IsDBNull(reader.GetOrdinal("NullableColumn")) Then
content = reader.GetInt32(reader.GetOrdinal("NullableColumn"))
End If
Or to keep it shorter
If Not reader.IsDBNull(reader.GetOrdinal("NullableColumn")) Then content = reader.GetInt32(reader.GetOrdinal("NullableColumn"))
But after that my variable content has the value 0, not Nothing as I would have expected.
How do you check the content value?
First of all, you should start with content.HasValue property. It should be False for your case of Nothing and True when correct value was fetched from database.
You should also get InvalidOperationException while accessing content.Value when it hasn't got value.
Chris has given the explanation but I dislike the style of assignment he’s chosen because it splits assignment from variable declaration.
By contrast, I recommend initialising variables upon declaration. In this case it’s admittedly slightly convoluted since you need cast either operator of If to the correct type first.
Dim content = If(reader.IsDBNull(reader.GetOrdinal("NullableColumn")),
DirectCast(Nothing, Integer?),
reader.GetInt32(reader.GetOrdinal("NullableColumn")))
Actually you can also use the slightly shorter New Integer?() instead of the DirectCast.
Of course now content is declared inside the Using block – this might not be what you want but you should try to make the declaration as local as possible.
Furthermore, this code is complex and will probably be reused. I suggest creating a separate (extension) method to convert database NULL values to nullables:
<Extension> _
Public Shared Function GetNullable(Of T)(SqlClient.SqlDataReader this, String fieldName) As T?
Dim i = this.GetOrdinal(fieldName)
Return If(this.IsDBNull(i), New T?(), this.GetFieldValue(Of T)(i))
End Function
Now you can use it as follows:
Dim content = reader.GetNullable(Of Integer)("NullableColumn")

String <> Nothing And String <> "" Redundant in VB .NET?

I looking at some code and came acorss this:
Dim TestString As String
...
If TestString <> Nothing And TestString <> "" Then
...
EndIf
Are both conditions checking the same thing?
Thanks
Nothing would be no string at all (null in other languages), which is different than an empty string (""), which is actually a string.
However, the check should be replaced with If Not String.IsNullOrEmpty(TestString) Then, which makes it clearer what exactly you are doing.
I just played around with this some in LINQPad, and found something mildly surprising. In VB.NET:
Dim s1 as string = Nothing
Dim s2 as string = ""
Console.WriteLine(s1 is Nothing) 'True
Console.WriteLine(s2 is Nothing) 'False
Console.WriteLine(s1 = "") 'True
Console.WriteLine(s2 = "") 'True
Console.WriteLine(string.IsNullOrEmpty(s1)) 'True
Console.WriteLine(string.IsNullOrEmpty(s2)) 'True
In C#:
string s1 = null;
string s2 = "";
Console.WriteLine(s1 == null); //True
Console.WriteLine(s2 == null); //False
Console.WriteLine(s1 == ""); //False
Console.WriteLine(s2 == ""); //True
Console.WriteLine(string.IsNullOrEmpty(s1)); //True
Console.WriteLine(string.IsNullOrEmpty(s2)); //True
I wasn't quite expecting that. It appears that VB.Net treats an Nothing as an empty string. My guess is for compatibility with older versions of VB.
This reinforces all the more that you should be using String.IsNullOrEmpty for these sorts of checks, as it is more explicit what you are checking for, and works as expected.
They are checking the samething, but they MIGHT be meant to check different things.
If IsNothing(TestString) Then
And
If TestString = Nothing Then
Are different test -- the first is seldom used, because typicaly you only really want to know if it has a non-empty value. But it can be used for treating an empty string anf a null value differently in a DB, or for detecting usage of an optional parameter (both cases requiring extra work to make sure that you don't inadvertantly slip in a wrong value, so somewhat fragile).
In the example given, the test is actually a bit wordy and confusing, if that's what you want to test then
If String.IsNullOrEmpty(TestString) Then
Is the way to go about it. If that "and" was supposed to be an "or" then it might make sense to have used IsNothing(TestString).
Yes, by definition in VB.NET "" is equivalent to Nothing including for =, <> and all VB functions; unless you explicitly care about the difference, such as by checking with Is.
Of course, you'll see differences when using general .NET functions and especially methods where str.Method will fail with a Null reference exception.
BTW I would guess the excerpt in the OP is C# code (badly) converted.

VB.NET - Want to add together two nullable types - how? (i.e. var1 + var2 where both are nullable and vari1=Nothing, Var2=5 results in Nothing)

I have:
Dim nVar1 As Long?
Dim nVar2 As Long?
Dim nVarSum As Long?
nVar1 = Nothing
nVar2 = 5
nVarSum = nVar1 + nVar2
I would prefer the result to end with nVarSum being 5, instead of Nothing.
I understand if you add something to an unknown value, you will end up with "somthing + unknown" or
x+5 will always equal "x+5" not "5" because you are still carrying around that unknown "x".
However, how can I effectively treat an unknown or Nothing as a zero for the purposes of addition in this case?
Thanks!
(What is basically happening is that the end user is sending us a data file, this code parses that file and then sums together about 15 fields. If the user leaves those fields blank instead of assigning a zero to them, I need to treat it as if it was a zero for this one addition operation, but all the rest of the code needs to continue seeing it as a Nothing value since the user did not ACTUALLY submit zero... they submitted blank or nothing)
nVar1.GetValueOrDefault()+ nVar2.GetValueOrDefault()
Or in c#:
(nVar1??0)+(nVar2??0)
I think the simplest way is to use the If operator to coerce Nothing values into a default one.
nVarSum = If(nVar1,0) + If(nVar2,0)
The If operator in the 2 argument form when applied to nullable types essentially does the following. If the nullable has a value then the return is the value, otherwise it's the second argument.
Or explicitly test for nothing and set your default value. Same result as other answers posted.
If nVar1 is nothing then
nVar1 = 0
end if
nVarSum = nVar1 + nVar2

Exception from VB.NET Comparison Statement

Here is the exception:
"Conversion from string "" to type 'Double' is not valid."
Here is the line of code that that throws the exception (confirmed from stepping through in the debugger):
If LoanData.Item("ApplicationId") <> "" AndAlso LoanData.Item("ApplicationId") IsNot DBNull.Value Then
Any ideas?
did you try LoanData.Item("ApplicationId").toString()?
LoanData.Item("ApplicationId") is returning a double probably.
You're trying to compare it with a String.
If this is the problem you could just do this:
LoanData.Item("ApplicationId").ToString() <> ""
The value stored in LoanData.Item("ApplicationId") must be of type Double.
The VB.NET compiler is letting you use the <> operator only because you must have Option Strict Off. It is assuming the value of LoanData.Item("ApplicationId") must be a String and is attempting to cast accordingly.
Since the value is not a String, you're getting an InvalidCastException.
Instead of using the <> operator, you can use Equals instead, which will result in a call to the Equals method of whatever type the value of LoanData.Item("ApplicationId") happens to have (or the Object.Equals method, if that type has not overridden it):
If Not LoanData.Item("ApplicationId").Equals("") AndAlso LoanData.Item("ApplicationId") IsNot DBNull.Value Then
This should exhibit the closest possible behavior to what you currently have without resulting in an exception.
It's worth noting, btw, that VB6 would allow one to compare a double to a string directly, but this broke the transitive property of comparisons since "9" would be greater than "8Q", and "8Q" would be greater than the double 10.2 (since the double would be converted to a string in that case), but the double 10.2 would be greater than "9" (since the string would be converted to a double in that case). Eeks.