I have a bit of code which loads data from a stored procedure in MS SQL Server and then loads the data into a DataGridView, which works fine. What I want is for the code that connects / loads the data to sit in my Database class and then everything associated with the DataGridView to be stored in my Form but I am having problems passing the contents of the BindingSource over to the Form from the Database class.
Form1 code:
Public Class Form1
Dim myDatabaseObj As New Class1()
Dim bindingSource1 As New BindingSource()
Dim connectString As New SqlConnection
Dim objDataAdapter As New SqlDataAdapter
Dim table As New DataTable()
Dim tabletest As New DataTable()
Private Sub loadCompanyList()
Try
Me.dgv_CompanyList.DataSource = Me.bindingSource1
getCompanyList()
Catch ex As NullReferenceException
End Try
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
loadCompanyList()
End Sub
End Class
When I try place the getCompanyList() in a class and then create a new object that references the Form() it does not seem to return any value from the table to the MyForm.BindingSource1.Datasource meaning my DataGridView displays no data.
Database class code:
.....
Private Sub getCompanyList()
Try
Dim myForm as new Form()
connect_Transaction_Database()
objDataAdapter.SelectCommand = New SqlCommand()
objDataAdapter.SelectCommand.Connection = connectString
objDataAdapter.SelectCommand.CommandText = "sp_GetCompanyList"
objDataAdapter.SelectCommand.CommandType = CommandType.StoredProcedure
Dim commandBuilder As New SqlCommandBuilder(Me.objDataAdapter)
Dim table As New DataTable()
table.Locale = System.Globalization.CultureInfo.InvariantCulture
Me.objDataAdapter.Fill(table)
**MyForm.bindingSource1.DataSource = table**
Catch ex As DataException
MsgBox(ex.Message)
Catch ex As NullReferenceException
MsgBox(ex.Message)
End Try
disconnect_Transaction_Database()
End Sub
This question has just been bumped by the Community user so I thought I would provide an answer which may help others in the future.
Firstly, I would consider implementing Using:
Managed resources are disposed of by the .NET Framework garbage collector (GC) without any extra coding on your part. You do not need a Using block for managed resources. However, you can still use a Using block to force the disposal of a managed resource instead of waiting for the garbage collector.
Secondly, you don't need to use a SqlDataAdapter. Instead you can load a DataTable using the SqlCommand class and return that. I would also contain the SqlConnection in the method rather than having it opened and closed in a different one.
Your code would look something like this:
Form1 code:
Public Class Form1
Private Sub loadCompanyList()
Dim myDatabase As New Database
Me.dgv_CompanyList.DataSource = myDatabase.getCompanyList()
End Sub
End Class
Database code:
Public Class Database
Public Function getCompanyList() As DataTable
Dim dt As New DataTable
Using con As New SqlConnection(connectionString),
cmd As New SqlCommand("sp_GetCompanyList", con) With {.CommandType = CommandType.StoredProcedure}
con.Open()
dt.Load(cmd.ExecuteReader())
End Using
Return dt
End Function
End Class
You want getCompanyList to be a function, which returns a DataTable. Then, forget the BindingSource (if the DataGridView is read-only) and set the DataSource property to the function:
Me.dgv_CompanyList.DataSource = getCompanyList
I would suggest you make the GetCompanyList method into a Function that returns the DataTable filled by the SqlDataAdapter. There's no real reason this Sub should have a dependency on the form. Instead a method in the form could call this to get the DataTable and then perform the binding.
Public Function GetCompanyList() As DataTable
...
...
Dim table As New DataTable()
table.Locale = System.Globalization.CultureInfo.InvariantCulture
Me.objDataAdapter.Fill(table)
Return table
...
...
Related
I am using Visual Studio, web forms, and vb.net. Why is my list count 0 outside of PopulateNames()?
I have a list at the top of my vb.net code:
Private defaultNames As List(Of ListItem) = New List(Of ListItem)()
I can add items to my list here:
'Populate cbNames with all names
Private Sub PopulateNames()
Using sqlConn As New SqlConnection(ConfigurationManager.ConnectionStrings("databasehere").ConnectionString)
sqlConn.Open()
Using sqlCmd As New SqlCommand()
sqlCmd.CommandText = "select [ID], [FirstName] from vwNames"
sqlCmd.Connection = sqlConn
Using sdr As SqlDataReader = sqlCmd.ExecuteReader()
While sdr.Read()
Dim item As New ListItem()
item.Text = sdr("FirstName").ToString()
item.Value = sdr("ID").ToString()
'Add name from database into checkboxlist
cbNames.Items.Add(item)
'Add item from checkboxlist cbNames into list defaultNames
defaultNames.Add(item)
End While
End Using
End Using
End Sub
defaultNames is count of 6 in PopulateNames() but count of 0 anywhere else. Why won't my list items save into my defaultNames list?
You need to read up on scope.
Private defaultNames As List(Of ListItem) = New List(Of ListItem)()
Is scoped to an instance of a class, and is initialized with a new list whenever the containing class is instantiated. For a form, that will most likely be whenever it is shown.
Even if you do want that private member and don’t want to expose it elsewhere, populating it the way you do is simply bad.
It should either be a parameter that gets filled, or it should be the result of a function, or the data reader should be returned and used to populate the list, or there should be a function passed in as a parameter that does something with it.
Also, although it entails more work, you probably shouldn’t be using a list item (a GUI class) whether this is a web form or winform applications. Which is why I prefer the last 2 option’s and think the last is generally best if you are going to do something with it yourself as opposed to passing it off to something else:
Private sub init()
PopulateNames(Sub(dr as IDataReader)
Dim item As New ListItem()
item.Text = dr("FirstName").ToString()
item.Value = dr("ID").ToString()
'Add name from database into checkboxlist
cbNames.Items.Add(item)
'Add item from checkboxlist cbNames into list defaultNames
defaultNames.Add(item)
End sub
End Sub
Private Sub PopulateNames(loader as Action(Of IDataReader))
Using sqlConn As New SqlConnection(ConfigurationManager.ConnectionStrings("databasehere").ConnectionString)
sqlConn.Open()
Using sqlCmd As New SqlCommand()
sqlCmd.CommandText = "select [ID], [FirstName] from vwNames"
sqlCmd.Connection = sqlConn
Using sdr As SqlDataReader = sqlCmd.ExecuteReader()
While sdr.Read()
loader(sdr)
End While
End Using
End Using
End Sub
Given this structure, if you make the PopulateNames method public, it can be used in multiple different ways. You are no longer limited to creating a list of ListItems, as all of the actual work is done in the lambda (or if you prefer in a separate function).
Assumption: This is a Winforms application.
Don't open the connection until directly before the .Execut.... To save a few indents, you can combine the connection Using and the command Using with a comma at the end of the connection Using. You can pass the CommandText and the Connection directly to the constructor of the Command.
You don't want to fill lists and, definitely, you don't want to update the user interface (comparatively, a very slow process) while the connection is open. Readers require an open connection. Just fill a DataTable and shut down the database objects.
To fill the CheckedListBox just set the DisplayMember, ValueMember and DataSource.
Private Sub PopulateNames()
Dim dt As New DataTable
Using sqlConn As New SqlConnection(ConfigurationManager.ConnectionStrings("databasehere").ConnectionString),
sqlCmd As New SqlCommand("select [ID], [FirstName] from vwNames", sqlConn)
sqlConn.Open()
Using sdr As SqlDataReader = sqlCmd.ExecuteReader()
dt.Load(sdr)
End Using
End Using
cbNames.DisplayMember = "FirstName"
cbNames.ValueMember = "ID"
cbNames.DataSource = dt
End Sub
ListItem is not a type available without a reference and Imports to a special library.
For a web application
Private Sub PopulateNames()
Dim dt As New DataTable
Using sqlConn As New SqlConnection(ConfigurationManager.ConnectionStrings("databasehere").ConnectionString),
sqlCmd As New SqlCommand("select [ID], [FirstName] from vwNames", sqlConn)
sqlConn.Open()
Using sdr As SqlDataReader = sqlCmd.ExecuteReader()
dt.Load(sdr)
End Using
End Using
cbNames.DataSource = dt
cbNames.DataTextField = "FirstName"
cbNames.DataValueField = "ID"
cbNames.DataBind()
End Sub
To access the DataTable you can either make a class level variable or create a Function and return the DataTable
Private Function GetNameData() As DataTable
Dim dt As New DataTable
Using sqlConn As New SqlConnection(ConfigurationManager.ConnectionStrings("databasehere").ConnectionString),
sqlCmd As New SqlCommand("select [ID], [FirstName] from vwNames", sqlConn)
sqlConn.Open()
Using sdr As SqlDataReader = sqlCmd.ExecuteReader()
dt.Load(sdr)
End Using
End Using
Return dt
End Function
Protected Sub Button1_Click(ByVal sender As Object, e As System.EventArgs) Handles Button1.Click
Dim dt = GetNameData()
cbNames.DataSource = dt
cbNames.DataTextField = "FirstName"
cbNames.DataValueField = "ID"
cbNames.DataBind()
End Sub
I hope the title is enough to understand my problem, I already installed whats need to run the ADO.NET, I already have a connection string in my module and data query in my class,
Imports System.Data
Imports System.Data.OleDb
Module GlobalVariables
Public sGlobalConnectionString As String
Friend conString As String
Public dr As OleDbDataReader
Sub Main()
Dim sGlobalConnectionString As New OleDb.OleDbConnection
Dim sDataserver As String
Dim sDatabaseName As String
Dim sDatabaseConnection As String
sDataserver = "localhost"
sDatabaseName = "employee"
sDatabaseConnection = "Driver={MariaDB ODBC 3.1 Driver}; SERVER=" & sDataserver & "; UID=root;PWD=******; Database=" & sDatabaseName & "; PORT=3307; OPTION=3"
sGlobalConnectionString = New OleDb.OleDbConnection(conString)
End Sub
End Module
this is my class
Imports System.Data.OleDb
Public Class clsDataQuery
Public Shared Sub Class_initialize()
Dim con = New OleDb.OleDbConnection
con.ConnectionString = sGlobalConnectionString
con.Open()
End Sub
Public Shared Sub Class_Terminate()
Dim con = New OleDb.OleDbConnection
If Not con Is Nothing Then
con.Close()
con = Nothing
End If
End Sub
Public Function GetRecordDataSet(ByVal sStoreProcName As String, ByVal sParameterList As String)
Dim cmd As New OleDbCommand()
Dim arrParameter, arrParamName
Dim sParamName As String
Dim sDataValue
Dim lCtr As Long
On Error GoTo errhandler
cmd.Connection = New OleDb.OleDbConnection
cmd.CommandTimeout = 1800
cmd.CommandText = CommandType.Text
If Not Trim(sParameterList) = "" Then
arrParameter = Split(sParameterList, "|", , vbTextCompare)
If UBound(arrParameter) >= 0 And IsArray(arrParameter) Then
For lCtr = 0 To UBound(arrParameter)
arrParamName = Split(arrParameter(lCtr), "$", , vbTextCompare)
sParamName = arrParamName(0)
sDataValue = arrParamName(1)
cmd.Parameters.Item(sParamName) = sDataValue
Next lCtr
End If
End If
GetRecordDataSet = cmd.ExecuteReader
cmd = Nothing
Exit Function
errhandler:
MessageBox.Show("Records Not Found!!!")
End Function
End Class
if this button is click, the value of Textbox1.text will search in the database if it is exist, if exist it will continue into another form if not error message will appear, how do i do that?
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim username = txtbox_lastname.Text
If username <> "" Then
Try
clsDataQuery.Class_initialize()
Catch ex As Exception
MessageBox.Show("No Record Found")
End Try
Else
MessageBox.Show("No Record Found!!!")
End If
End Sub
If this is MariaDb then you want to use the provider for MySql. Not ODBC and not OleDb. No wonder you are having problems. There is not much information available for this database compared to the usual Access database used for beginners.
Do not declare database objects anywhere but the method they are used in. You can declare a Private variable for the connection string at the class level.
Although one of these is a Module level variable and one is a local variable, it is very confusing and bad practice. Why would you call a connection object a String?
Public sGlobalConnectionString As String
Dim sGlobalConnectionString As New OleDb.OleDbConnection
BTW, it is fine to declare and initialize your variables in a single line.
Dim sDataserver = "localhost"
You create a new connection on the first line of Sub Main, then you throw it away and create another new connection on the last line. Since sDataServer and sDatabaseName are hard coded why not just put the literal values directly into the connection string.
After all that you pass conStr to the constructor of the connection instead of sDatabaseConnection. Why were you building sDatabaseConnection (another misnomer, it is not a connection, its a string) and then never use it. Has conStr been set elsewhere?
At any rate, throw out the whole module.
Moving on to your DataQuery class. First, the name should begin with an upper case letter.
Get rid of Class_initialize. We don't want to create or open any connection except in the method where it is used. You never call Class_Terminate so dump that too.
The GetRecordDataSet method...
Functions in vb.net require a datatype. The old VB6 syntax of assigning the return value to the name of the function will work but it is not the .net way. In vb.net we use the Return keyword.
You have not initialized or given a datatype to arrParameter, arrParamName or sDataValue which violates Option Strict. (You do have Option Strict On, don't you?)
On Error GoTo errhandler is a sad leftover from VB6. .net languages have structured error handling with Try...Catch...Finally...End Try.
cmd.Connection = New OleDb.OleDbConnection sets the connection property however this new connection has no connection string.
cmd.CommandText = CommandType.Text Now this is just silly. What I think you want is cmd.CommandType =CommandType.StoredProcedure
Using...End Using blocks take care of declaring, closing and disposing database objects even if there is an error. You don't want to return a DataReader because a reader requires an open connection. cmd.ExecuteReader returns a DataReader. I used the reader to load a DataTable and returned the DataTable.
It seems you are trying to develop a factory pattern but it is way too advanced for your. Just pass value and call a method specific to what your are searching for. You want your DataQuery code to be independent from your user interface. The Button code doesn't care where the data is coming from. It could be a database, a text file, or a web service. Likewise the DataQuery code doesn't know where the values are coming from. It could be a WinForms app, a web application or a phone app.
Public Class DataQuery
Private Shared ConStr As String = "server=localhost;userid=root;database=employee"
Public Shared Function SearchByLastName(ByVal LName As String) As DataTable
Dim dt As New DataTable
Using cn As New MySqlConnection(ConStr),
cmd As New MySqlCommand("Select * From PutTableNameHere Where LastName = #LName", cn)
cmd.Parameters.Add("#LName", MySqlDbType.VarChar).Value = LName
cn.Open()
Using reader = cmd.ExecuteReader
dt.Load(reader)
End Using
End Using
Return dt
End Function
End Class
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim dt As DataTable = Nothing
If txtbox_lastname.Text <> "" Then
Try
dt = DataQuery.SearchByLastName(txtbox_lastname.Text)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End If
DataGridView1.DataSource = dt
End Sub
Before answering, I really think that the GetRecordDataSet() at the very least a darn good tidy up, better yet removed from history
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim username = txtbox_lastname.Text
If username <> "" Then
Try
clsDataQuery.Class_initialize()
Dim reader = GetRecordDataSet("storedProcName", txtbox_lastName.Text)
Do While reader.Read()
'Process record etc
Loop
Catch ex As Exception
MessageBox.Show("No Record Found")
End Try
Else
MessageBox.Show("No Record Found!!!")
End If
End Sub
Might be a bit rough, but should get you heading in the right direction
I'm trying to load data from a SQL Server database into a DataGridView using threading with delegates and later will try to add a loading or waiting form. But I can't make it work.
Dim t1 As Thread
Private Delegate Sub delegate_updatedgv()
Private Sub mainform_Load(sender As Object, e As EventArgs) Handles MyBase.Load
t1 = New Thread(AddressOf load_dgv)
t1.Start()
End Sub
Private Function load_dgv()
Try
If DBCon.State = ConnectionState.Closed Then
DBCon.Open()
End If
DBCmd = New SqlCommand("SELECT * FROM Jobs ", DBCon)
DBCmd.ExecuteNonQuery()
Dim DBDT = New DataTable
Dim DBDA = New SqlDataAdapter(DBCmd)
Dim RecordCount = DBDA.Fill(DBDT)
assignit(DBDT)
DBCon.Close()
Catch ex As Exception
MsgBox(ex.Message)
Finally
If DBCon.State = ConnectionState.Open Then DBCon.Close()
End Try
End Function
Sub assignit(ByVal x As DataTable)
If Me.InvokeRequired Then
Me.Invoke(New delegate_updatedgv(AddressOf assignit))
End If
GridControl1.DataSource = x
End Sub
and its not working at all
Your delegate
Private Delegate Sub delegate_updatedgv()
and your method
Sub assignit(ByVal x As DataTable)
don't have the same signature. Actually the compiler already tells you this in an error message...
You could just use Action(Of DataTable). There is no need to create a new delegate.
You also needed to set the DataSource only when invoking on the UI thread. You can achieve that by using Else.
Sub assignit(ByVal x As DataTable)
If Me.InvokeRequired Then
Me.Invoke(New Action(Of DataTable)(AddressOf assignit))
Else
GridControl1.DataSource = x
End If
End Sub
Alternately, you can also declare the delegate properly. Either way
Private Delegate Sub delegate_updatedgv(x As DataTable)
Also, the method / function Private Function load_dgv() has no return type, and nothing is returned. So it is better suited as a sub.
Private Sub load_dgv()
' ...
End Sub
The compiler also warns you about this.
Pay attention to compiler error messages!
While we are at, let's fix up the database code a bit. There is no need to check connection state when database objects are keep local to the method where they are used. This way you can ensure that they are closed and disposed with a Using...End Using block.
I have no idea why you are calling .ExecuteNonQuery on this command. You never use RecordCount so I don't see the point of that either. I just used one line to load the DataTable. The adapter seemed extraneous.
Private Sub load_dgv()
Dim dt As New DataTable
Using DBCon As New SqlConnection("Your connection string"),
DBCmd As New SqlCommand("SELECT * FROM Jobs ", DBCon)
DBCon.Open()
dt.Load(DBCmd.ExecuteReader)
End Using
assignit(dt)
End Sub
I know this didn't answer your threading question but I hope it helped for data access.
Where is the bottleneck in this code? Is it just the retrieval of data or is it updating the UI? Maybe only the data access needs to run on a separate thread.
I'm trying to use this code in module page and I called it from FrmEmployee using button to show all Employee information in a DataGridView`.
OpenDB is a function to open connection using OleDbConnection.
Function New1()
Dim strcmd As String = New String("Select * From Employee")
Co.CommandText = strcmd
Co.Connection = Conn
da.SelectCommand() = Co
OpenDB()
da.Fill(ds, "Employee")
emp.DataGridView1.DataSource = ds.Tables("Employee")
End Function
Name your methods so they have meaning. Functions need a return type. "Get" implies that the method will return something and in this case it returns a DataTable of employees.
Keep you database objects local. Then you can control that they closed and disposed. `Using...End Using blocks do this for you even it there is an error.
You can pass the connection string (or a variable containing the string) directly to the construtor of the connection. Likewise, pass the sql statement and connection to the constructor of the command.
Keep the code in your DataAccess module separate from your User Interface code in the form. Don't update you user interface from the module.
Imports System.Data.SqlClient
Module Module2
Function GetEmployeeDataTable() As DataTable
Dim dt As New DataTable
Using cn As New SqlConnection("Your connection string")
Using cmd As New SqlCommand("Select * From Employee", cn)
cn.Open()
dt.Load(cmd.ExecuteReader)
End Using
End Using
Return dt
End Function
End Module
Then in the Form
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
DataGridView1.DataSource = Module2.GetEmployeeDataTable()
End Sub
I am trying to populate a listbox by retrieving data from a database through sql. I have asked this question earlier but i was using a different configuration and the one i'm using now isn't giving any results.
retrieving data in VB from SQL
That is my old post. I will now provide the code to the newer version of my attempt.
Imports System.Data.Sql
Imports System.Data.SqlClient
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim conn As New SqlConnection
conn.Open()
Dim comm As New SqlCommand("SELECT name FROM Table_1", conn)
Dim reader As SqlDataReader = comm.ExecuteReader
Dim dt As New DataTable
dt.Load(reader)
ListBox1.Items.Add(dt)
End Sub
End Class
If anyone would be willing to help me out i'd greatly appreciate it. If possible, use a practical approach when trying to enlighten me, as that works best.
edit 1
Imports System.Data.Sql
Imports System.Data.SqlClient
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim connString As String = "Data Source=THE_SHOGUNATE\SQLEXPRESS;Initial Catalog=le_database;Integrated Security=True"
Dim conn As New SqlConnection(connString)
conn.Open()
Dim comm As New SqlCommand("SELECT name FROM Table_1", conn)
Dim reader As SqlDataReader = comm.ExecuteReader
Dim dt As New DataTable
dt.Load(reader)
ListBox1.DataSource = dt
End Sub
End Class
With this code the listbox populates with 6 instances of "System.Data.DataRowView" strings, 6 being the number of items in my table. How do i get the actual values?
You missed the connectionString
If you want to populate list from DB there are many ways
With DataReader
Imports System.Data.Sql
Imports System.Data.SqlClient
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim connectionString As String = "Data Sourec=localhost;........."
Dim conn As New SqlConnection(connectionString)
conn.Open()
Dim comm As New SqlCommand("SELECT name FROM Table_1", conn)
Dim reader As SqlDataReader = comm.ExecuteReader
/* As it is not working i commented this
listBox1.ItemsSource = dt; // use this instead of ListBox1.Items.Add(dt)
//because Add event add only one item in the list.
*/
Dim i As Integer
i=0
while reader.read()
listbox1.Items.Add(dr(i).ToString);
i++
End While
End Sub
End Class
With DataTable
Imports System.Data.Sql
Imports System.Data.SqlClient
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim connectionString As String = "Data Sourec=localhost;........."
Dim conn As New SqlConnection(connectionString)
conn.Open()
// Create new DataAdapter
SqlDataAdapter a = new SqlDataAdapter("SELECT * FROM EmployeeIDs", c)
// Use DataAdapter to fill DataTable
DataTable dt = new DataTable();
a.Fill(dt);
ListBox1.DataSource = dt;
ListBox1.DataTextField = "name";
End Sub
End Class
EDIT:
Other parameters of connection string depends on your security and all that. You must see this link Connection strings for SQL Server 2008
Set the DisplayMember property after DataSource binding:
ListBox1.DataSource = dt
ListBox1.DisplayMember="name"
The last solution I saw should work, but there are a couple important best practices to keep in mind regarding SQL Server.
1) Avoid Select * whenever possible, instead name your columns explicitly. Select * causes SQL to perform extra work if you do not intend to pull down all the columns in the table. It's also not future-proof, since a dba could add a VARBINARY(MAX) column in the future, and populate it with gigs worth of blob data, This scenario would make your query as written slow down substantially and unnecessarily.
2) Always remember to close your SQLConnection when you're done with it. This will free up a SQL connection and resources.
if (cn.State != ConnectionState.Closed)
cn.Close();
Another cool trick is to use the USING directive which will dispose of the SqlConnection object when execution passes out of scope.
using (SqlConnection cn = new SqlConnection(sConnectionString))
{
if (cn.State != ConnectionState.Open)
cn.Open();
// add query code here.
if (cn.State != ConnectionState.Closed)
cn.Close();
}
don't forget to close your SqlDataReader after the read loop is complete.
if (!dr.IsClosed)
dr.Close();
I hope this helps.
Andre Ranieri