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.
Related
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
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)
I have some old code that I'm running through to make sure it's as stable as possible. Once in a blue moon, something breaks and leaves a bunch of garbage data in the database so I'm going through to see where a potential error could occur. I've found a bunch of variable assignments that look something like this:
myVariable = dr.Item("myItem") & ""
If dr.Item("myItem") resolves to Null, would the & "" protect myVariable from being Null and set it to "" or would the Null cause me problems?
You're guaranteed to get a string back, whether dr.Item("myItem") is a string, Nothing or DBNull.Value. As per the documentation for the & Operator:
The data type of result is String. If one or both expressions evaluate to Nothing or have a value of DBNull.Value, they are treated as a string with a value of "".
You don't need & operator and extra instance of type String, which concatenate operator will create.
Use .ToString() method
dr.Item("myItem").ToString()
DataRow.Item property return instance of type Object. Then calling ToString() method of that instance will return what you expected in your case (Item contain value of type String)
If item contains DbNull value, then empty string will be returned
DBNull.ToString Method
Or use extension method .Field(Of T)
dr.Field(Of String)("myItem")
DataRowExtensions.Field(Of T) Method (DataRow, String)
If column doesn't exist then Column not in the table Exception will be thrown
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")
I have a dataset containing a datatable, and I enumerate all rows in that datatable. When trying to format a column in that row, I run into an exception. (Part of) the code is:
For Each dr As DataRow In ds.Tables("records").Rows
file = dr("timestamp").ToString("yyyyMMdd") & "~.wav"
Next
This results in the following error message:
Conversion from string yyyyMMdd to type Integer is not valid.
(translated from a Dutch error message to the English equivalent)
dr("timestamp").GetType.FullName results in "System.DateTime", so I don't understand why I run into this exception, as for example Now.ToString("yyyyMMdd") results in "20091002", and "Now" is of the same type as dr("timestamp"), "System.DateTime" that is.
Try
For Each dr As DataRow In ds.Tables("records").Rows
file = CDate(dr("timestamp")).ToString("yyyyMMdd") & "~.wav"
Next
I've encountered this issue too, although in my case the value in question was a nullable Date/DateTime.
As well as casting to a Date, as another answer has suggested, you can also call .Value to get a non-nullable value (after making sure it isn't actually null, of course).
Have you made sure that you are not declaring the variable 'file' as an integer?