How can I store the results of a DataReader into an array, but still be able to reference them by column name? I essentially want to be able to clone the DataReader's content so that I can close the reader and still have access. I don't want to store the items in a DataTable like everyone suggests.
I've seen a lot of answers, but I couldn't really find any for what I wanted
The easiest way I've found to do this is by populating the array with dictionaries with Strings as keys and Objects as values, like so:
' Read data from database
Dim result As New ArrayList()
Dr = myCommand.ExecuteReader()
' Add each entry to array list
While Dr.Read()
' Insert each column into a dictionary
Dim dict As New Dictionary(Of String, Object)
For count As Integer = 0 To (Dr.FieldCount - 1)
dict.Add(Dr.GetName(count), Dr(count))
Next
' Add the dictionary to the ArrayList
result.Add(dict)
End While
Dr.Close()
So, now you could loop through result with a for loop like this:
For Each dat As Dictionary(Of String, Object) In result
Console.Write(dat("ColName"))
Next
Quite similar to how you would do it if it were just the DataReader:
While Dr.Read()
Console.Write(Dr("ColName"))
End While
This example is using the MySQL/NET driver, but the same method can be used with the other popular database connectors.
Related
I am currently working on an application that was coded in VB. I am making modifications and adding features to it.
The issue I have is that I want to run a verification check for the ComboBox based on if the value exists before I attempt to select it.
The combo box is populated from a sql query with a dictionary data source
Dim TimerComboSource As New Dictionary(Of String, String)()
TimerComboSource.Add(varSQLReader("ID").ToString, varSQLReader("Name").ToString)
'Binds the values to the comboboxes
cmbTimer.DataSource = New BindingSource(TimerComboSource, Nothing)
cmbTimer.DisplayMember = "Value"
cmbTimer.ValueMember = "Key"
I select a value from a different ComboBox which is populated with a different SQL Query/SQL Table.
When I select the second ComboBox value, the table it comes from contains the ID of the first ComboBox. I want to verify if the Value Exists in the first ComboBox before I select it.
The following does not work:
If cmbTechnician.Items.Contains(varSQLReader("Tech_ID").ToString) Then
cmbTechnician.SelectedValue = varSQLReader("Tech_ID").ToString
End If
Is there a specific way in VB to make this work without it being overly complicated? The other work around would to make a more complicated SQL query but I rather not do that if there's a simpler way.
Since you are using a BindingSource on a Dictionary, you should use the DataSource to determine of things exist. If you tried to add to cmbTimer.Items or delete from it directly, you'd get an error telling you to use the DataSource. So do the same for checking if something exists (dont use a dictionary with local scope):
' form or class level collection
Private cboSource As New Dictionary(Of String, String)
cboSource.Add("red", "red")
cboSource.Add("blue", "blue")
cboSource.Add("green", "green")
cbo.DataSource = New BindingSource(cboSource, Nothing)
cbo.DisplayMember = "Value"
cbo.ValueMember = "Key"
If cbo.Items.Contains("red") Then
Console.Beep() ' wont hit
End If
If cboSource.ContainsValue("red") Then
Console.Beep() ' hits!
End If
The suggestion in comments suggests casting the DataSource of the BindingSource back to dictionary:
Dim tempBS As BindingSource = CType(cbo.DataSource, BindingSource)
Dim newCol As Dictionary(Of String, String) = CType(tempBS.DataSource, Dictionary(Of String, String))
If newCol.ContainsValue("red") Then
Console.Beep() ' hits!
End If
It is easier and more direct to retain a reference to the dictionary, but recreating it will work.
Here are another way =>
If cmbTechnician.FindString(varSQLReader("Tech_ID").ToString) <= -1 Then
'Do Not Exist
End If
This will find the display member and will return an index of the row if there exist value, otherwise will return -1.
More Info are here=>ComboBox.FindString
i am a noob, so please take this question with that in mind. Here is very simple piece of code:
Sub main()
{
Dim m_Dictionary as new Dictionary(Of Integer, List(Of String))
Dim workingList as new List(Of String)
Dim workingKey as Integer
Dim keyStash as List(Of Integer)
Dim workingDict as new Dictionary(Of Integer, String)
For i=0 to 9
Do
workingKey = RandomInteger()
Loop While workingList.ContainsKey(workingKey)
For n=0 to 4
workingList.Add(RandomString())
Next
keyStash.Add(workingKey)
workingDict.Add(workingKey, workingList)
Next
' now I just want to play back the generators of random data
For each Key As Integer in keyStash
For each Entry as String in workingDict(Key).Value
Line(Entry)
Next
Next
Instead of everything playing back nicely as one might expect, I am left with a fully accurate stash of keys for the dictionary. However, the values for strings inside each list instance are ALL THE SAME FOR EVERY KEY. Those values are equal to the values in the last loop of random data generation. So instead of playing back 50 uniques entries, it writes out 9 times the last loop. I looked inside - everything looks good. Get this. All lists, collections, hash-tables, all of iterated types and also custom types demonstrate this behavior. I found the solution, but it does not explain anything. Can anyone help explaining this, please!??
The variable that keeps the strings generated by RandomString is created outside the loop. Inside that loop you add continuosly new strings to the same instance and add the same list instance to every new integer key. At the end of the loop every integer key added has its value pointing to the same reference of the list. Of course they are identical....
A first fix to your code could be
Dim m_Dictionary as new Dictionary(Of Integer, List(Of String))
Dim workingKey as Integer
For i=0 to 9
' Internal to the loop. so at each loop you get a new list
' to use for the specific key generated in the current loop
Dim workingList as new List(Of String)
Do
workingKey = RandomInteger()
Loop While m_Dictionary.ContainsKey(workingKey)
For n=0 to 4
workingList.Add(RandomString())
Next
m_Dictionary.Add(workingKey, workingList)
Next
For each k in m_Dictionary
For each Entry in k.Value
' Line(Entry)
Console.WriteLine("Key=" & k.Key & " Values = " & Entry)
Next
Next
Please, remember to use Option Strict ON, the current code treats quietly strings as if they were numbers, and this is not a good practice. Option Strict ON will force you to think twice when you work with different type of data.
I'm trying to solve a problem regarding types of list. First of all I have a stored procedure in my DB which does a select of a single column and I try to proceed it in my app in VB. By making a method function I declared a DataTable that loads through the SqlCommand(with the CloseConnection behavior). After that I publicly declared a List(Of String) which needs to be populated with the rows/items from the stored procedure that is on the way. Below is my snippet of the code:
Dim dt As New DataTable()
Try
If conn.State = ConnectionState.Open Then
conn.Close()
Else
conn.Open()
Dim cmd = New SqlCommand("LoadCodes", conn)
cmd.CommandType = CommandType.StoredProcedure
dt.Load(cmd.ExecuteReader(CommandBehavior.CloseConnection))
Dim collection As New List(Of DataRow)
collection = dt.AsEnumerable.ToList
LPrefix = collection.Cast(Of String)()
End If
Catch ex As Exception
MsgBox(ex.Message + vbCritical)
End Try
It's LPrefix = collection.Cast(Of String)() where I get an exception error telling me that I can't really convert it. The old fashion way is to iterate with for/for each loop but that's not what I want for best use of performance especially if the list will have thousands of rows from a single column. So basically I want to insert those items from that DataTable to the List(Of String) without using For/For Each loop.
Running on Visual Studio 2010 Ultimate, .NET Framework 4.0.
You don't need your collection at all. Using LINQ, you can extract the first column directly out of your data table:
dt.Load(cmd.ExecuteReader(CommandBehavior.CloseConnection))
LPrefix = (From row In dt.AsEnumerable()
Select row.Field(Of String)(0)).ToList()
Of course, this might use a loop internally, but since you want to copy each value into a list of strings, you cannot do it without looping through the data rows.
Another alternative would be to use an IEnumerable(Of String) instead of a List(Of String):
dt.Load(cmd.ExecuteReader(CommandBehavior.CloseConnection))
Dim LPrefixNew As IEnumerable(Of String) = _
From row In dt.AsEnumerable()
Select row.Field(Of String)(0)
You can iterate through IEnumerable just as you would through a list, but evaluation is lazy: As long as you don't access the elements, the DataTable is not traversed. So, accessing this IEnumerable is like reading the elements directly from the DataTable, just in a more convenient way.
Another word of advice: You should not try to reason about performance until you have measured it. For example, your line collection = dt.AsEnumerable.ToList probably already loops through your entire DataTable and copies each DataRow reference into a List of DataRows; so, with this line, you already have the performance penalty that you are trying to avoid.
So, don't automatically assume that some For loop is always slower than some single statement. Measure it, then optimize.
Assuming your DataRow only has one column you just need to instruct ConvertAll to cast it:
LPrefix = collection.ConvertAll(Function(x) x[0].ToString)
Thanks to Binary Worrier for c#-2-vb translation!
Currently we have a large DataTable (~152k rows) and are doing a for each over this to find a sub set of distinct entries (~124K rows). This is currently taking about 14 minutes to run which is just far too long.
As we are stuck in .NET 2.0 as our reporting won't work with VS 2008+ I can't use linq, though I don't know if this will be any faster in fairness.
Is there a better way to find the distinct lines (invoice numbers in this case) other than this for each loop?
This is the code:
Public Shared Function SelectDistinctList(ByVal SourceTable As DataTable, _
ByVal FieldName As String) As List(Of String)
Dim list As New List(Of String)
For Each row As DataRow In SourceTable.Rows
Dim value As String = CStr(row(FieldName))
If Not list.Contains(value) Then
list.Add(value)
End If
Next
Return list
End Function
Using a Dictionary rather than a List will be quicker:
Dim seen As New Dictionary(Of String, String)
...
If Not seen.ContainsKey(value) Then
seen.Add(value, "")
End If
When you search a List, you're comparing each entry with value, so by the end of the process you're doing ~124K comparisons for each record. A Dictionary, on the other hand, uses hashing to make the lookups much quicker.
When you want to return the list of unique values, use seen.Keys.
(Note that you'd ideally use a Set type for this, but .NET 2.0 doesn't have one.)
I have a small requirement in VB.NET and that is to fill an array with values retrieved from a table. That is i have a table called "user_roles" and that is having columns called "role_id" and "role_name". In the load event of the form, i want to execute a procedure and populate all the items (role_id) from the table "user_roles" into an array.
Can anyone please help me on this requirement.
Regards,
George
I assume that you better use a generic list instead of an array. Correct me if i'm wrong.
If you have filled your table in your codebehind, you can add the rolw_id's by iterating through all rows.
Dim allRoleIDs As New List(Of Int32)
For Each row As DataRow In user_roles.Rows
allRoleIDs.Add(CInt(row("role_id)")))
Next
Consider that its better to use a Datareader here because of performance reasons.
When you are using a strong typed dataset and want to avoid the extra roundtrip after filling the Datatable to add the ID's to the list, you have to extend the auto-generated Dataset DataAdapter Class(f.e. called user_rolesTableAdapter).
Don't use the Dataset's designer.vb class for that, because it will be overridden on every Dataset change. Use its codebehind class(without designer.vb) and add first the same namespace from your auto-generated TableAdapter(f.e. DatasetNameTableAdapter where DatasetName is the name of your Dataset). Then add following class(replace correct commands,column-index,class name of the partial class):
Namespace DatasetNameTableAdapters
Partial Public Class user_rolesTableAdapter
Public Function getListOfUserRolesID() As System.Collections.Generic.List(Of System.Int32)
Dim list As New System.Collections.Generic.List(Of System.Int32)
Dim command As System.Data.SqlClient.SqlCommand = Me.CommandCollection(0)
Dim previousConnectionState As System.Data.ConnectionState = command.Connection.State
If ((command.Connection.State And System.Data.ConnectionState.Open) _
<> System.Data.ConnectionState.Open) Then
command.Connection.Open()
End If
Try
Using reader As System.Data.SqlClient.SqlDataReader = command.ExecuteReader
While reader.Read
list.Add(reader.GetInt32(0))
End While
End Using
Finally
If (previousConnectionState = System.Data.ConnectionState.Closed) Then
command.Connection.Close()
End If
End Try
Return list
End Function
End Class
End NameSpace
Now you can get the user_roles ID's as generic List directly from the Dataadapter without iterating twice(first on filling the datatable and second on addind the ID's to an List).
Of course you can also use this Datareader approach in Page.Load without using a Dataset.