Access the contents of a column in a DataTable in code - mysql

I've done research on this but none of results found does what I'm looking for.
Suppose I have a DataTable that was filled from a MySql database, in another function, I want to access one of the columns and assign the values to a variable (one at time in a loop)
Some code to illustrate what I'm trying to do:
Dim adapter As New MySqlDataAdapter
Dim dt As New DataTable
Dim intList As New List(Of Integer)
.......
.......
adapter.fill(dt)
.......
.......
dim col = dt.Columns(1)
populate the list here with the contents of the column
EDIT I am not entirely sure that Columns(1) is correct syntax

(Referring to the other answer) First, note that there is no need to separately declare scratch variables used to loop thru something. You can declare the type as part of the For...:
For Each row As DataRow In Dt.Rows
This is a more than a matter of coding style. The row variable above exists only between the For Each and Next. Declared as in the other answer, a more lengthy method can result in tmp, tmp1 etc each of a different type which were used just once for different loops.
Then, there are a number of linq methods that can get your list for you without you writing a loop at all:
Dim carbs = dt.AsEnumerable().
Select(Function(q) q.Field(Of Int32)("Carbs")).
ToList()
With Option Infer, you don't even need to declare carbs as a List(of Int32), but you can:
Dim carbs As List(Of Int32) = dt.AsEnumerable(). ...

I found what I was looking for here, I don't know how I missed that in my seach.
The code in the end is the following:
Dim carbsInvolved As New List(Of Integer)
Dim row As DataRow
For Each row In dt.Rows
carbsInvolved.Add(row.Item(1))
Next
And then the numbers in the list can be accessed by carbsInvolved.Item(index) for later use :)

Related

Populating ListBox More Quickly

Is there a way to make populating ListBox fast, because the UI is freezing on form load upon populating the ListBox?
This is my form load code:
Dim abc As String = itemCount()
Dim output = Account_Get(a)
For Each s In output
ListBox1.Items.Add(s)
count1 += 1
If count1 = abc Then
ListBox1.Visible = True
End If
Next
This is the query in module:
Public Function Account_Get(ByVal chk As String) As List(Of String)
Dim result = New List(Of String)()
Try
cn.Open()
sql = "select column_name as str from table where status = 'New' order by rand()"
cmd = New MySqlCommand(sql, cn)
dr = cmd.ExecuteReader
While dr.Read
result.Add(dr("str").ToString())
End While
Return result
Catch ex As Exception
MsgErr(ex.Message, "Error Encounter")
Return Nothing
Finally
cn.Close()
End Try
End Function
this is working fine. but the fact that it loads too many datas. the ui is freezing on load. hoping someone could help me with this. thanks!
Since you are incrementing count1 I assume it is some sort of number. However, you are then comparing it to a string in the If statement. Please use Option Strict.
Changed the Function to return an Array of String. Took the random sort form the sql statement and moved it to a little linq at the end or the function.
You could add a Stopwatch to the data retrieval and the display sections to see where your bottleneck is. BeginUpdate and EndUpdate on the listbox prevents repainting on every addition.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim output = Account_Get()
ListBox2.BeginUpdate()
ListBox2.Items.AddRange(output)
ListBox2.EndUpdate()
End Sub
Private Rand As New Random
Public Function Account_Get() As String()
Dim dt As New DataTable
Dim result As String()
Using cn As New MySqlConnection("Your connection string")
Dim Sql = "select column_name as str from table where status = 'New'" 'order by rand()"
Using cmd = New MySqlCommand(Sql, cn)
Try
cn.Open()
dt.Load(cmd.ExecuteReader)
Catch ex As Exception
MessageBox.Show(ex.Message, "Error Encounter")
Return Nothing
End Try
End Using
End Using
result = (From dRow In dt.AsEnumerable()
Let field = dRow("str").ToString
Order By Rand.Next
Select field).ToArray
Return result
End Function
The query you are using contains a random order. Ordering records randomly can be a huge performance issue within MySQL as it has to go through all records in the table and then sort them randomly. The more records in the table, the bigger the performance penalty. There is also no limitation on the number of records in your query. So if there are thousands of items in your table the listbox will also be thousands of items in size, which could also take a long time.
If you really require the random ordering you could do something about it in your code. I'm now assuming here that you are: 1) using identifiers in your table, 2) you actually wish to limit the number of items in your listbox and not display all of them.
Get a grasp of the total number of records in the table by a query
Pick a random number from the range of items in your table
Fetch the nearest record
Hope this helps you to get going to find a solution

tableadapter not deleting programatically removed datatable rows

I have a dataset linked with a MYSQL database via tableadpators and a TableAdpaterManager (all autographed through designer view). The visual studio route was setting up a DataSource and then dragging individual tables onto the form.
Now, I can update, insert and delete records in the database if I edit the datagridviews manually. However, if I remove datarows programatically, the rows are not deleted in the database.
The removal code:
Public Shared Sub RemoveDuplicateRowsViaField(dt As DataTable, RowName As String)
Dim rowList As New List(Of String)
Dim dr As DataRow
For i = dt.Rows.Count - 1 To 0 Step -1
dr = dt(i)
If rowList.Contains(dr.Item(RowName)) Then
dt.Rows.Remove(dr)
Else
rowList.Add(dr.Item(RowName))
End If
Next
dt.DataSet.AcceptChanges()
End Sub
And the 'save' code:
Private Sub SaveOrganisationsBT_Click(sender As Object, e As EventArgs) Handles SaveOrganisationsBT.Click
Me.Validate()
Me.Gi_usersBindingSource.EndEdit()
Me.TableAdapterManager.UpdateAll(Me.dbDS)
End Sub
I've tried many things including looking into the tableAdapters in dataset design view, but am at a loss. Can anyone help?
Nevermind. Solved it. It appears to be around the use of AcceptChanges on the datasource. The code below works. I've read that datarow.remove both deletes the row and accepts changes. I'm guessing if changes are accepted then the tableadapter can't discern it needs deleting from its non 'dirty' state, given changes have been accepted, thus clearing the status of the row? Just supposition. The working code:
Public Shared Sub RemoveDuplicateRowsViaField(dt As DataTable, RowName As String)
Dim rowList As New List(Of String)
Dim dr As DataRow
For i = dt.Rows.Count - 1 To 0 Step -1
dr = dt(i)
If rowList.Contains(dr.Item(RowName)) Then
dr.Delete()
Else
rowList.Add(dr.Item(RowName))
End If
Next
End Sub

Access VBA - can't access Series.Name in a chart

I am trying to use VBA to colour the series in a chart by matching the names of the series to a table containing the colour values for each series (tblTypes). My only problem is retrieving the names of the series, which I've consistently read is accessed by SeriesCollection(index).Name. The weird thing is, SeriesCollection() doesn't seem to be returning a Series object. Here's the relevant code:
Private Sub ReformatColoursByType(grphChart As Object)
Dim rs As DAO.Recordset
Dim iii As Integer
Dim objSeries As Series
Set rs = CurrentDb.OpenRecordset("tblTypes", RecordsetTypeEnum.dbOpenDynaset)
rs.MoveFirst
Do
For iii = 1 To grphChart.SeriesCollection.Count
Set objSeries = grphChart.SeriesCollection(iii) -- !!! This line is the problem !!! --
If objSeries.Name = rs!rwType Then
... formatting ...
End If
Next iii
rs.MoveNext
Loop Until rs.EOF
End Sub
The line highlighted above returns a "type mismatch" error - grphChart.SeriesCollection(iii) is not of type "Series" for some reason. What's gone wrong?
A couple of things that are wrong.
In your code grphChart is a ChartObject - a container for the chart, not the chart itself.
Instead use:
grphChart.Chart.SeriesCollection.Count
and
grphChart.Chart.SeriesCollection(iii)
The second problem is Dim objSeries As Series. You've tagged the question as Access and, I think, your code is referencing Excel.
If it's late binding then Access won't understand what a Series is - you need to define it as an Object.

"Out of stack space" with a Recursive function in vba for Access

I'm reaching out to you because I'm getting troubles coding a module for an Access program.
Introduction:
I got 4 Tables: Products, Receipes, Ordonnancement & Commands.
Ordonnancement and Commands have the same structure, the second one being the result of the processing of the commands through the receipes.
Goal of the VBA Module:
I'm creating a Module to create records to the Ordonnancement table by processing the commands through the receipes. In detail, I use a recursive function to cope with the variable deepth of the receipes that allows me to loop through the Receipes Table and generate the need in all Products for one date.
Remarks:
-I normally work in C# using EF to work with databases. After several tries to use directly the RecordSet possibilities of Access, I decided to generate POCO classes for ReceipeLign and OrdoLign, to stock the data of the tables in collections of those objects, work with those and then commit to the Access tables adding records to each RecordSet.
-I work in french, so I translated a few things so it can be understood by everyone. It might not be perfect, let me know if not clear.
Code:
Option Compare Database
Option Explicit
Dim cnc As New ADODB.Connection
Dim CRecordSet As New ADODB.Recordset
Dim FTRecordSet As New ADODB.Recordset
Dim ORecordSet As New ADODB.Recordset
Public Sub GenerateOrdonnancement()
'Retrieving info from tables Commandes & FT in RecordSets.
Set cnc = CurrentProject.Connection
Set CRecordSet = cnc.Execute("SELECT * FROM Commandes")
Set FTRecordSet = cnc.Execute("SELECT * FROM FichesTechniques")
Set ORecordSet = cnc.Execute("SELECT * FROM Ordonnancement")
'Creation of the list to receive data from the tables
Dim Commandes As New Collection
Dim FicheTechniques As New Collection
'Retrieving commands and receipes
Dim Commande As ligneOrdo
Dim ordo As ligneOrdo
Dim FT As ligneFT
Do Until CRecordSet.EOF
Set Commande = New ligneOrdo
Commande.DateCommande = CRecordSet("dateCommande").Value
Commande.Produit = CRecordSet("Produit").Value
Commande.Quantite = CRecordSet("quantite").Value
Commandes.Add Commande
CRecordSet.MoveNext
Loop
CRecordSet.Close
Do Until FTRecordSet.EOF
Set FT = New ligneFT
FT.Nom = FTRecordSet("Nom").Value
FT.Ingredient = FTRecordSet("Ingredient").Value
FT.Quantite = FTRecordSet("quantité").Value
FT.IsComposed = FTRecordSet("Composé").Value
FicheTechniques.Add FT
FTRecordSet.MoveNext
Loop
FTRecordSet.Close
'creation of the collection of ordo
'Later: versionning of the Ordonnancements
Dim AProduire As New Collection
Dim mr As ligneOrdo
For Each mr In Commandes
Dim coll As Collection
Set coll = CreateOrdoLigne(mr, FicheTechniques)
Dim item As New ligneOrdo
For Each item In coll
AProduire.Add item
Next item
Next mr
'Adding and saving the coll AProduire in the RecordSetO
cnc.BeginTrans
Dim item2 As ligneOrdo
For Each item2 In AProduire
ORecordSet.AddNew
ORecordSet("DateCommande").Value = item2.DateCommande
ORecordSet("Produit").Value = item2.Produit
ORecordSet("Quantite").Value = item2.Quantite
ORecordSet.Update
Next item2
ORecordSet.Close
cnc.CommitTrans
End Sub
Function CreateOrdoLigne(ligne As ligneOrdo, FT As Collection) As Collection
Dim ordo As New Collection
Dim ligneFT As Variant
'Loop through the receipes
For Each ligneFT In FT
If ligneFT.Nom = ligne.Produit Then
Dim AProduire As New ligneOrdo
AProduire.Produit = ligneFT.Ingredient
AProduire.DateCommande = ligne.DateCommande
AProduire.Quantite = ligne.Quantite * ligneFT.Quantite
ordo.Add AProduire
If ligneFT.IsComposed = True Then
Dim ordoList2 As New Collection
Set ordoList2 = CreateOrdoLigne(AProduire, FT)
Dim recordOrdo As ligneOrdo
For Each recordOrdo In ordoList2
ordo.Add recordOrdo
Next recordOrdo
Set ordoList2 = Nothing
End If
Set AProduire = Nothing
End If
Next ligneFT
Set CreateOrdoLigne = ordo
End Function
Problem Statement:
Running the Module, I get a Run-Time Error 28 : "Out of stack Space", which seems after some reseach a common thing working with recursive functions in such tight environnements. Problem is, I can't really optimize the process. I am looking for direct ways to bypass this error or ideas to tackle this problem in another way.
Thank you all,
So after some debuging with #Andre 's help, I found out that the recursivity was infinite, hence the error on the size.
Even with that, Access was not able to generate so much data and stock it somewhere beforme commiting those changes to the database.
I have found a way around that problem, which consists in:
avoiding using functions to be stocked in a collection. I therefore transformed the function into a sub.
commiting the changes to the database along the generation process.
obj = Nothing when it is not needed anymore in a repeatable process.
What I have learnt and maybe could help others
Analyse the recursive process to see what could make it infinite, define error handlers accordingly
Debug.Print is an efficient way to debug in vba ACCESS, to generate data and check the whole process.
Thank you #Andre and the others for your time, hope it will help others.

LINQ to SQL datagridview query returns length, not value

I've got a Windows Form with a button and a datagridview. The project includes a working database connection and LINQ to SQL class. I'm trying to bind the datagridview to the LINQ to SQL.
In a code module I've got this:
Public Function DataGridList() As BindingSource
Dim NewBindingSource As New BindingSource()
Dim db As New DataClasses1DataContext()
NewBindingSource.DataSource = _
From Block In db.BLOCK_ASSIGNMENTs
Where Block.gr912_school = "Franklin"
Select Block.gr6_school Distinct
Return NewBindingSource
End Function
And this button_click code in the form:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
DataGridView1.DataSource = DataGridList()
End Sub
When I click the button I get the length of the school names in the datagridview, with a column header of "length."
If I just run this very similar code in the button_click instead, the school names appear correctly in the immediate window:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim db As New DataClasses1DataContext()
Dim TestQuery =
From Block In db.BLOCK_ASSIGNMENTs
Where Block.gr912_school = "Franklin"
Select Block.gr6_school Distinct
For Each block In TestQuery
Debug.Print(block)
Next
End Sub
Give this a try:
Public Function DataGridList() As BindingSource
Dim NewBindingSource As New BindingSource()
Dim db As New DataClasses1DataContext()
NewBindingSource.DataSource = _
From Block In db.BLOCK_ASSIGNMENTs
Where Block.gr912_school = "Franklin"
Select New With { Key .Value = Block.gr6_school } Distinct
Return NewBindingSource
End Function
This should give it a property that the DataGridView can pick up on. The New With... creates an anonymous object with a Property named Value. The DataGridView works on this type of object by enumerating the public properties and rendering them as columns. If you had needed more than one value, you could have added additional items inside the curly braces in the same way separated by commas. See Anonymous Types (Visual Basic) for more information.
You might try adding a .ToString to the Select:
From Block In db.BLOCK_ASSIGNMENTs
Where Block.gr912_school = "Franklin"
Select Block.gr6_school.ToString Distinct
I believe that Debug.Print does an implicit conversion to .ToString when it prints to the immediate window. However, a datagrid cell treats everything as an object and displays the default property for that object.
Turns out, of course, this has been addressed often, including on SO, and here. The route I chose was to use an intermediate DataTable:
Public Function DataGridList() As DataTable
Dim NewDataTable As New DataTable
Dim db As New DataClasses1DataContext()
Dim i As Int32
Dim qry =
From Block In db.BLOCK_ASSIGNMENTs.AsEnumerable
Where Block.gr912_school = "Franklin"
Select Block.gr6_school Distinct
NewDataTable.Columns.Add("School")
For i = 0 To qry.Count - 1
NewDataTable.Rows.Add(qry(i))
Next
Return NewDataTable
End Function
This seems pretty slow the first time it runs, so I may try something else in the future, but it allows me to feed the grid via LINQ, which is what I want to work with.
(I would like to use the qry's CopyToDataTable property, but it's only available if the query returns a DataTableRows collection, or some such, and my hacking around didn't reveal how to do that.)