"System.NullReferenceException" when trying to fill array - vb.net

This is my code:
Public UserBookings() As String
If dr.Read() Then
Dim intCounter As Integer
intCounter = 0
While dr.Read()
UserBookings(intCounter) = dr(0)
intCounter = intCounter + 1
End While
Return UserBookings
Else
Return False
End If
But I get this error at runtime:
A first chance exception of type 'System.NullReferenceException' occurred in Project.exe
And the array doesn't even seem to fill up.
Anything I can do to fix this? Bear in mind it is a dynamic array (I'm not sure how many entries it will have) so I don't see how I can loop through every entry and give it an initial value.
Thanks.

Among the problems listed as possible answers here, I also see that you are returning False at one point when return type of the method is string. That is not allowed. You can return Nothing or String.Empty, but not False.

It seems as though you dr(0) is an empty object, but try adding .ToString() to the end of it. But of course it depends on what datatype UserBookings() is.
Upon speaking with my colleague
you should probably use dr.HasRows instead of "if dr.read()" because you are losing an entire row.

The easiest option here is to realize that you can use an alternative construct such as an ArrayList (or better, a List(of String))
This way, the object will dynamically grow until you've loaded all the items, and you can return the result as an array
Two possible options below (the generic List(Of x) is preferred.
Function ReadArray(ByVal dr As IDataReader) As String()
Dim tempstorage As New List(Of String)
While dr.Read()
tempstorage.Add(dr(0))
End While
Return tempstorage.ToArray()
End Function
--
Function ReadArray2(ByVal dr As IDataReader) As String()
Dim tempstorage As New ArrayList()
While dr.Read()
tempstorage.Add(dr(0))
End While
Return tempstorage.ToArray(GetType(String))
End Function
--
Note: I've added some additional code (assuming that dr is a DataReader, and that you're calling this as a function

Related

2-D array from txt in VB.NET

I am needing to create a code that is versatile enough where I can add more columns in the future with minimum reconstruction of my code. My current code does not allow me to travel through my file with my 2-D array. If I was to change MsgBox("map = "+ map(0,1) I can retrieve the value easily. Currently all I get in the code listed is 'System.IndexOutOfRangeException' and that Index was outside the bounds of the array. My current text file is 15 rows (down) and 2 columns (across) which puts it at a 14x1. they are also comma separated values.
Dim map(14,1) as string
Dim reader As IO.StreamReader
reader = IO.File.OpenText("C:\LocationOfTextFile")
Dim Linie As String, x,y As Integer
For x = 0 To 14
Linie = reader.ReadLine.Trim
For y = 0 To 1
map(x,y) = Split(Linie, ",")(y)
Next 'y
Next 'x
reader.Close()
MsgBox("map = " + map(y,x))``
Here's a generic way to look at reading the file:
Dim data As New List(Of List(Of String))
For Each line As String In IO.File.ReadAllLines("C:\LocationOfTextFile")
data.Add(New List(Of String)(line.Split(",")))
Next
Dim row As Integer = 1
Dim col As Integer = 10
Dim value As String = data(row)(col)
This is the method suggested by Microsoft. It is generic and will work on any properly formatted comma delimited file. It will also catch and display any errors found in the file.
Using MyReader As New Microsoft.VisualBasic.
FileIO.TextFieldParser(
"C:\LocationOfTextFile")
MyReader.TextFieldType = FileIO.FieldType.Delimited
MyReader.SetDelimiters(",")
Dim currentRow As String()
While Not MyReader.EndOfData
Try
currentRow = MyReader.ReadFields()
Dim currentField As String
For Each currentField In currentRow
MsgBox(currentField)
Next
Catch ex As Microsoft.VisualBasic.
FileIO.MalformedLineException
MsgBox("Line " & ex.Message &
"is not valid and will be skipped.")
End Try
End While
End Using
Essentially what you are asking is how can I take the contents of comma-separated values and convert this to a 2D array.
The easiest way, which is not necessarily the best way, is to return an IEnuemrable(Of IEnumerable(Of String)). The number of items will grow both vertically based on the number of lines and the number of items will grow horizontally based on the values split on a respective line by a comma.
Something along these lines:
Private Function GetMap(path As String) As IEnumerable(Of IEnumerable(Of String)
Dim map = New List(Of IEnumerable(Of String))()
Dim lines = IO.File.ReadAllLines(path)
For Each line In lines
Dim row = New List(Of String)()
Dim values = line.Split(","c)
row.AddRange(values)
map.Add(row)
Next
Return map
End Function
Now when you want to grab a specific cell using the (row, column) syntax, you could use:
Private _map As IEnumerable(Of IEnumerable(Of String))
Private Sub LoadMap()
_map = GetMap("C:/path-to-map")
End Sub
Private Function GetCell(row As Integer, column As Integer) As String
If (_map Is Nothing) Then
LoadMap()
End If
Return _map.ElementAt(row).ElementAt(column)
End Function
Here is an example: https://dotnetfiddle.net/ZmY5Ki
Keep in mind that there are some issues with this, for example:
What if you have commas in your cells?
What if you try to access a cell that doesn't exist?
These are considerations you need to make when implementing this in more detail.
You can consider the DataTable class for this. It uses much more memory than an array, but gives you a lot of versatility in adding columns, filtering, etc. You can also access columns by name rather than index.
You can bind to a DataGridView for visualizing the data.
It is something like an in-memory database.
This is much like #Idle_Mind's suggestion, but saves an array copy operation and at least one allocation per row by using an array, rather than a list, for the individual rows:
Dim data = File.ReadLines("C:\LocationOfTextFile").
Select(Function(ln) ln.Split(","c)).
ToList()
' Show last row and column:
Dim lastRow As Integer = data.Count - 1
Dim lastCol As Integer = data(row).Length - 1
MsgBox($"map = {data(lastRow)(lastCol)}")
Here, assuming Option Infer, the data variable will be a List(Of String())
As a step up from this, you could also define a class with fields corresponding to the expected CSV columns, and map the array elements to the class properties as another call to .Select() before calling .ToList().
But what I really recommend is getting a dedicated CSV parser from NuGet. While a given CSV source is usually consistent, more broadly the format is known for having a number of edge cases that can easily confound the Split() function. Therefore you tend to get better performance and consistency from a dedicated parser, and NuGet has several good options.

VB.Net Use XmlNodeList in a parallel ForEach

I have a piece of code that iterates over the nodes in an XmlNodeList and creates a different object for each one depending on the node name and adds it to a list for printing.
For Each node as XmlNode In nodeList
Select Case node.Name.ToUpper()
Case "SHAPE"
_items.Add(New ShapeTemplate(node, Me))
Case "TEXTBLOCK"
_items.Add(New TextblockTemplate(node, Me))
End Select
Next
This code works fine, but because of all the work that has to be done by the ShapeTemplate and TextblockTemplate constructors, it is VERY slow. Since the order objects are added to _items doesn't matter, I thought a good way to speed it up would be to use a parallel.ForEach loop. The problem is XmlNodeList can't be used with parallel.ForEach because it is a non-generic collection. I've been looking into ways to convert XmlNodeList to List(Of XmlNode) with no luck. The answer I keep seeing come up is
Dim nodes as New List(Of xmlNode)(nodeList.Cast(Of xmlNode)())
But when I try it, I get an error telling me that 'Cast' is not a member of XmlNodeList.
I've also tried using TryCast like this
Dim nodes as List(Of XmlNode) = TryCast(CObj(nodeList), List(Of XmlNode))
but it results in nodes being Nothing because the object can't be cast.
Does anyone know how I can use XmlNodeList in a parallel.ForEach loop?
EDIT: I'm trying to avoid using a loop for the conversion if I can
You could use Parallel LINQ instead of Parallel.ForEach, which seems like a more natural fit for this sort of transformation. This looks just like a normal LINQ query, but with .AsParallel() added.
Imports System.Linq
' _items will not be in same order as nodes in nodeList.
' Add .AsOrdered() after .AsParallel() to maintain order, if needed.
_items = (
From node In nodeList.Cast(Of XmlNode)().AsParallel()
Select item = CreateTemplate(node)
Where item IsNot Nothing
).ToList()
Function CreateTemplate(node As XmlNode) As ITemplate ' interface or base class for your types
Dim item As ITemplate
Select Case node.Name.ToUpper()
Case "SHAPE"
item = New ShapeTemplate(node, Me)
Case "TEXTBLOCK"
item = New TextblockTemplate(node, Me)
Case Else
item = Nothing
End Select
Return item
End Function
As seen here, the XmlNodeList can be converted to a generic List(Of XmlNode) by passing it to the constructor with OfType.
' I added so your code could compile.
' I assumed an interface shared by ShapeTemplate and TextblockTemplate
Dim nodeList As XmlNodeList
Dim _items As New List(Of ITemplate)
Dim _nodes As New List(Of XmlNode)(nodeList.OfType(Of XmlNode))
Now the parallel loop. Note, if you are adding to a non-threadsafe collection such as List, you will need to synchronize adding the objects to the list. So i separated the time consuming portion (Template constructor) from the fast operation (adding to the list). This should improve your performance.
Dim oLock As New Object
Parallel.ForEach(
_nodes,
Sub(node)
Dim item As ITemplate
Select Case node.Name.ToUpper()
Case "SHAPE"
item = New ShapeTemplate(node, Me)
Case "TEXTBLOCK"
item = New TextblockTemplate(node, Me)
Case Else
item = Nothing ' or, do something else?
End Select
SyncLock oLock
_items.Add(item)
End SyncLock
End Sub)

Reflection Optimization

I've recently implemented reflection to replace the more tedious aspects of our data retrieval from a SQL database. The old code would look something like this:
_dr = _cmd.ExecuteReader (_dr is the SQLDataReader)
While _dr.Read (_row is a class object with public properties)
_row.Property1 = Convert.ToInt16(_dr("Prop1"))
_row.Property2 = Convert.ToInt16(_dr("Prop2"))
_row.Property3 = Convert.ToInt16(_dr("Prop3"))
If IsDBNull(_dr("Prop4")) = False Then _row.Prop4 = _dr("Prop4")
...
Since my code base has a lot of functionality like this, reflection seemed like a good bet to simplify it and make future coding easier. How to assign datareader data into generic List ( of T ) has a great answer that was practically like magic for my needs and easily translated into VB. For easy reference:
Public Shared Function GenericGet(Of T As {Class, New})(ByVal reader As SqlDataReader, ByVal typeString As String)
'Dim results As New List(Of T)()
Dim results As Object
If typeString = "List" Then
results = New List(Of T)()
End If
Dim type As Type = GetType(T)
Try
If reader.Read() Then
' at least one row: resolve the properties
Dim props As PropertyInfo() = New PropertyInfo(reader.FieldCount - 1) {}
For i As Integer = 0 To props.Length - 1
Dim prop = type.GetProperty(reader.GetName(i), BindingFlags.Instance Or BindingFlags.[Public])
If prop IsNot Nothing AndAlso prop.CanWrite Then
props(i) = prop
End If
Next
Do
Dim obj = New T()
For i As Integer = 0 To props.Length - 1
Dim prop = props(i)
If prop Is Nothing Then
Continue For
End If
' not mapped
Dim val As Object = If(reader.IsDBNull(i), Nothing, reader(i))
If val IsNot Nothing Then SetValue(obj, prop, val)
Next
If typeString = "List" Then
results.Add(obj)
Else
results = obj
End If
Loop While reader.Read()
End If
Catch ex As Exception
Helpers.LogMessage("Error: " + ex.Message + ". Stacktrace: " + ex.StackTrace)
End Try
Return results
End Function
The only caveat with this is that it is somewhat slower.
My question is how to optimize. Sample code I find online is all in C# and does not convert neatly into VB. Scenario 4 here seems like exactly what I want, but converting it to VB gives all kinds of errors (Using CodeFusion or converter.Telerik.com).
Has anyone done this in VB before? Or can anyone translate what's in that last link?
Any help's appreciated.
Couple ideas for you.
Don't use the DataReader when reading ALL records at once, it is slower than using a DataAdapter.
When you use the DataAdapter to fill a DataSet, you can iterate through the rows and columns which does NOT use reflection and will be much faster.
I have a program I created (and many other programmers do this too) that generate the code from a database for me. Each table and row is a class that is specifically named an generated in such a way that I can use intellisense and prevents many run-time errors by making them compile-time errors when data changes. This is very much like the EntityFramework but lighter because it fits MY specific needs.

Dealing with Nothing in the middle of a string of calls

I am a relative VB.Net noob, and I'm learning by doing. I'm sure what I'm about to ask has been asked 10^19 times before, but whatever code word it's under, I can't figure out how to Google it. Here goes...
We have an object model with one or more Project objects that consists of several Tables, which contain Rows which have Fields. This leads to code all over our apps that looks something like this...
Dim theColor As String = Projects(1).Tables(5).Rows(22).Fields(3)
In our application, if any of the objects in this "call chain" does not exist, the correct value for theColor should be Nothing*. This is just like Excel - the value of an empty cell in an empty row is vbnull, not "Excel has crashed".
This is not how VB.Net works, however. If, for instance, Rows(22) does not exist, the Fields(3) is called on Nothing and an exception is thrown. My question is how to best deal with this...
1) I could check each value to see it it's not Nothing, but that leads to horrible amounts of code...
If Projects(1) IsNot Nothing AndAlso Projects(1).Tables(5) AndAlso...
We have thousands of these, the amount of code this would require would be enormous.
2) I could wrap all accessors in try/catch, but that's really just a different sort of (1)
3) I could have a special instance of each object that has empty values. So, for instance, Tables(5) returns NullTable and Row(22) returns NullRow. But this means I have to always use accessor methods, I can't just look in the underlying arrays. You're probably saying good, but sadly a lot of our older code does just that (yes, duh).
4) Something else entirely? Am I missing some magic that everyone other than me knows?
You could have a function called GetField
Instead of
Dim theColor As String = Projects(1).Tables(5).Rows(22).Fields(3)
You would have
Dim theColor As String = GetField(1, 5, 22, 3)
Function GetField(ByVal projectIndex As Integer, ByVal tableIndex As Integer, ByVal rowIndex As Integer, byVal fieldIndex As Integer) As Object
If Projects(projectIndex) Is Nothing Then
Return Nothing
End If
If Projects(projectIndex).Tables(tableIndex) Is Nothing Then
Return Nothing
End If
If Projects(projectIndex).Tables(tableIndex).Rows(rowIndex) Is Nothing Then
Return Nothing
End If
If Projects(projectIndex).Tables(tableIndex).Rows(rowIndex).fields(fieldIndex) Is Nothing Then
Return Nothing
End If
Return Projects(projectIndex).Tables(tableIndex).Rows(rowIndex).fields(fieldIndex)
End Function
But I got to say... what you are doing looks sloppy. You should think of using classes with properties.
You could concoct a "project manager" of sorts. It is hard to know how viable this is from the post, but something like:
Class ProjectManager
Public Property CurrentProject As ProjectThing
Public Property CurrentTable As Integer
Get
Return tblIndex
End Get
Set
If CurrentProject IsNot Nothing Then
If CurrentProject.Tables(value) Is Nothing Then
Throw exception
Else
tblIndex = value
End If
End If
End Set
End Property
' etc
Then use it to store the current reference to the project and/or table. All the Is Not Nothings can be embedded there.
Private myPrj As New ProjectManager
...
myPrj.CurrentProject = Project(1)
myPrj.CurrentTable = 5
With the "manager" doing all the checking for everyone, you dont have to (much):
Dim theColor As String = myPrj.Rows(22).Fields(3)
or
Dim theColor As String = myPrj.GetRowValue(22, 3)
What it would really be doing is storing a validated object references for you, and testing those not worth storing. It could go as deep as you needed. Even if all it really did was encapsulate those Is Nothing/Is Not Nothing tests, it might add some value.
Public Function GetRowValue(r As Integer, f as Integer) As Something
If r < CurrentProject.Tables(tblIndex).Rows.Count AndAlso
f < CurrentProject.Tables(tblIndex).Rows(r).Fields.Count Then
Return ...
'or
Public Function GetRowValue(Of T)(r As Integer, f as Integer) As T
Once a project is specified, it could expose helpful properties like TableCount. It is possible that the data represented by some of the most used, most important Const definitions, could be exposed as properties:
' swap a property interface for "3"
Dim theColor As String = myPrj.FooColor(22)
You can handle the exception:
Dim theColor As String = Nothing
Try
theColor = Projects(1).Tables(5).Rows(22).Fields(3)
Catch
End Try
If you want to do it 'properly' you should specify the exceptions you are guarding against:
Dim theColor As String = Nothing
Try
theColor = Projects(1).Tables(5).Rows(22).Fields(3)
Catch ex As IndexOutOfRangeException
Catch ex As NullReferenceException
End Try

avoid checking for DataRow.IsDBNull on each column?

My code is 2x longer than it would be if I could automatically set IsDBNull to "" or simply roll over it without an error.
This is my code:
Dim conn As New SqlConnection
conn.ConnectionString = Module1.DBConn2
Dim sqlCommand = New SqlCommand("SELECT * FROM table", conn)
conn.Open()
Dim sqlDataset As DataSet = New DataSet()
Dim sqlDataAdapter As SqlDataAdapter = New SqlDataAdapter(sqlCommand)
sqlDataAdapter.Fill(sqlDataset)
conn.Close()
For Each rs As DataRow In sqlDataset.Tables(0).Rows
If Not IsDBNull(rs("column")) Then
Response.Write(rs("column"))
Else
Response.Write("")
End If
Response.Write("some stuff to write")
If Not IsDBNull(rs("column2")) Then
Response.Write(rs("column2"))
Else
Response.Write("")
End If
Next
In that case I'd just like to type Response.Write(rs("column")) instead of the If statement, and if column IsDBNull then output an empty string.
How can I do this?
Many thanks in advance!
You could simply use String.Join and pass row.ItemArray:
For Each row As DataRow In sqlDataset.Tables(0).Rows
Response.Write(String.Join("", row.ItemArray))
Next
That works since DBNull.ToString returns an empty string.
If you want to address every column, you can use the strongly typed DataRowExtensions.Field method which supports nullables and return null/Nothing for string. Then you could use the null-coalescing operator (?? in C#, If in VB).
Dim rowInfo = String.Format("{0}{1}{2}",
If(row.Field(Of String)("Column1"), ""),
If(row.Field(Of String)("Column2"), ""),
If(row.Field(Of String)("Column3"), ""))
However, note that String.Format will convert null/Nothing to "" implicitely anyway, so the If is redundant and just fyi.
MSDN:
If the object specified by index is a null reference (Nothing in
Visual Basic), then the format item is replaced by the empty string
("").
Here's a one-liner:
Response.Write(rs.IsNull("column") ? "" : rs("column"));
or make it an extension method:
public string GetValueOrBlankString(this DataRow rs, string column)
{
return rs.IsNull(column) ? "" : rs(column).ToString();
}
then call it as:
Response.Write(rs.GetValueOrBlankString("column"));
Dataset Extensions give you a clean way of doing and it's also strongly typed. The type must match the column type in the database though. If the database column can be null, then use a nullable type like below. The null values become Nothing for the returned nullable type.
For Each rs As DataRow In sqlDataset.Tables(0).Rows
'If string, you can use this. Null becomes nothing for the string.
Response.Write(rs.field(of String)("column"))
'if it's another type
Response.Write(rs.field(of Integer?)("column"))
Next
Ceres's answer is probably the best given that it avoids any sort of null testing, but it's worth noting that the 'IIF' function would also work pretty well her. It's still going to do the test for null but it's much more compact than how Joe was originally doing it. Something like this should do the trick:
For Each rs As DataRow In sqlDataset.Tables(0).Rows
Response.Write( IIF( IsDBNull(rs("column")), "", rs("column") ) )
Next
What's neat with this is you can substitute the "" for whatever you want to output if the value is in fact null ( a nice little added bonus. )
Here's some info on the 'IIF' function for those who don't know what it is:
http://msdn.microsoft.com/en-ca/library/27ydhh0d(v=vs.71).aspx