Protecting Variable From Null Value - vb.net

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

Related

VB.Net - Can you access the expected data type within a function?

I was wondering if there is any way to access the expected data type within a function similar to an event arg. I am doubtful that this is possible, though it would be an excellent feature.
I frequently work with (old and disorganized)Mysql databases creating interfaces through VB.Net. Often I will have an optional field which contains a NULL value in the database. I am frequently dealing with errors due to NULL and dbnull values in passing data to and from the database.
To complicate things, I often am dealing with unexpected datatypes. I might have an integer zero, a double zero, an empty string, or a string zero.
So I spend a fair amount of code checking that each entry is of the expected type and or converting NULLs to zeros or empty strings depending on the case. I have written a function ncc(null catch convert) to speed up this process.
Public Function ncc(obj As Object, tp As Type) As Object 'Null Catch Convert Function...
My function works great, but I have to manually set the type every time I call the function. It would be so much easier if it were possible to access the expected type of the expression. Here is an example of what I mean.
Dim table as datatable
adapter.fill(table)
dim strinfo as string
dim intinfo as long
strinfo = ncc(table.Rows(0).Item(0),gettype(String)) 'here a string is expected
intinfo = ncc(table.Rows(0).Item(0),gettype(Long)) 'here a long is expected
It would be so much more efficient if it were possible to access the expected type directly from the function.
Something like this would be great:
Public Function ncc(obj As Object, optional tp As Type = nothing) As Object
If tp Is Nothing Then tp = gettype(ncc.expectedtype)
That way I do not have to hard code the type on each line.
strinfo = ncc(table.Rows(0).Item(0))
You can make the ncc function generic to simplify calling it:
Public Function ncc(Of T)(obj As T) As T
If DbNull.Value.Equals(obj) Then Return Nothing
Return Obj
End Function
This kind of function will be able to in some cases infer the type, but if there's any possibility of null you'll still want to include a type name (because DBNull will be the inferred type for those values). The advantage is not needing to call gettype() and so gaining a small degree of type safety:
strinfo = ncc(Of String)(table.Rows(0).Item(0))
But I think this has a small chance to blow up at run time if your argument is not implicitly convertible to the desired type. What you should be doing is adding functions to accept a full row and return a composed type. These functions can exist as static/shared members of the target type:
Shared Function FromDataRow(IDataRow row) As MyObject
And you call it for each row like this:
Dim record As MyObject = MyObject.FromDataRow(table.Rows(i))
But, you problem still exists.
What happens if the column in the database row is null?
then you DO NOT get a data type!
Worse yet? Assume the data column is null, do you want to return null into that variable anyway?
Why not specify a value FOR WHEN its null.
You can use "gettype" on the passed value, but if the data base column is null, then you can't determine the type, and you right back to having to type out the type you want as the 2nd parameter.
You could however, adopt a nz() function (like in VBA/Access).
So, this might be better:
Public Function ncc(obj As Object, Optional nullv As Object = Nothing) As Object
If obj Is Nothing OrElse IsDBNull(obj) Then
Return nullv
End If
Return obj
End Function
So, I don't care if the database column is null, or a number, for such numbers, I want 0.
So
dim MyInt as integer
Dim MyDouble As Double
MyInt = ncc(rstData.Rows(0).Item("ContactID"), 0)
MyDouble = ncc(rstData.Rows(0).Item("ContactID"), 0)
dim strAddress as string = ""
strAddress = ncc(rstData.Rows(0).Item("Address"), "")
Since in NEAR ALL cases, you need to deal with the null from the DB, then above not only works for all data types, but also gets you on the fly conversion.
I mean, you CAN declare variables such as integer to allow null values.
eg:
dim myIntValue as integer?
But, I not sure above would create more problems than it solves.
So,
You can't get exactly what you want, because a function never has knowledge of how it's going to be used. It's not guaranteed that it will be on the right-hand side of an assignment statement.
If you want to have knowledge of both sides, you either need to be assigning to a custom type (so that you can overload the assignment operator) or you need to use a Sub instead of an assignment.
You could do something like this (untested):
Public Sub Assign(Of T)(ByVal field As Object, ByRef destination As T,
Optional ByVal nullDefault As T = Nothing)
If TypeOf field Is DBNull Then
destination = nullDefault
Else
destination = CType(field, T)
End If
End Sub
I haven't tested this, so I'm not completely certain that the compiler would allow the conversion, but I think it would because field is type Object. Note that this would yield a runtime error if field is not convertible to T.
You could even consider putting on a constraint requiring T to be a value type, though I don't think that would be likely to work because you probably need to handling String which is a reference type (even though it basically acts like a value type).
Because the destination is an argument, you wouldn't ever need to specify the generic type argument, it would be inferred.

Why doesn't a String of Nothing cause cause an exception? (BC42104) [duplicate]

This question already has answers here:
Difference between Equals/equals and == operator?
(11 answers)
Closed 3 years ago.
If I declare a string but do not assign a value, the Equals function throws an exception but it doesn't throw an exception if it is compared to a value.
The error list warns about the problem:
Warning BC42104 Variable a is used before it has been assigned a
value. A null reference exception could result at runtime.
Dim a As String
Dim b as string = "bar"
a.Equals("foo") 'causes System.NullReferenceException
a = "foo" 'No exception although a is nothing
a = b 'No exception although a is nothing
I know the warning says it COULD cause an exception, but does anyone know why this is the happening?
This is because Dim a As String declares the type of variable a but doesn't assign anything to it. This is basically saying : "This variable was made to hold a String object, but it doesn't hold any right now". On the other hand, Dim b As String = "bar" declares the variable and its type, but also assigns a String object to it ("bar"). The reason a.Equals("foo") returns an exception is because you only declared it without assigning anything to it (so you are trying to access an object that isn't there). a = "foo" works because you are assigning a String object of value "foo" to the variable a. It's like saying : "This variable now holds a String object with value 'foo'".
Edit :
While your code points to the assignment of the variable a, I was made aware that you wanted to know why the = operator, as a comparison operator, works. This is because what I said earlier isn't entirely true when I said it didn't hold an object. It actually is of Nothing value (which sets it as a null reference) if no String object was assigned to it (String is a nullable object).
See : https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/nothing
Hope this helps.

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

Difference between nothing and system.DBNull

I am working in VB.NET and I am wondering about the difference between Nothing and System.DBNull.
When I fire save query at that time I am giving value from grid at runtime like as follow:
gvMain.Rows(j).Cells("Brand").Value.ToString()
But it shows me error when it has value of Nothing and it works perfectlly when it has value of System.DBnull.
What to do in this case?
Thanks in advance
The keyword Nothing is used to specify or asign that a var of reference type is not pointing anything, no object is instanciated for this var.
DBNull.Value, on the other hand, is an object used to point out that a type of a field of the DataBase is of null value.
Nothing is of Type System.Object.
It represents the default value of any data type.
DBNull.Value is of Type System.DBNull
If something shows up as System.DBNull, that means that even though it doesn't have a value, it has a valid pointer. As you may have found out, it cannot be converted to a string, integer, etc. You must do a check (preferably using IsDBNull.
If IsDBNull(gvMain.Rows(j).Cells("Brand").Value) Then
Return String.Empty
Else
Return gvMain.Rows(j).Cells("Brand").Value.ToString().Trim()
End If

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