I'm using next sub to fill combobox by using datatable through dataadapter:
Public Sub Me_Sub_CboFill(ByVal Cbo As ComboBox, ByVal SqlStr As String, ByVal Dm As String, ByVal Vm As String)
Cbo.DataSource = Nothing
xAdapter = New MySqlDataAdapter(SqlStr, Conn)
Dim Dt As New DataTable
xAdapter.Fill(Dt)
If Dt.Rows.Count = 0 Then Conn.Close()
Cbo.DataSource = Dt
Cbo.DisplayMember = Dm
Cbo.ValueMember = Vm
End Sub
but I face next MSG:
unable to connect to any of the specified MySQL hosts
but when I open the connection manually, it works!!
I know that dataadapter with (Fill) opens and closes the connection itself but I didn't know why that happened with my code.
by the way, I tried many ways to test that but with the same result, like next code:
dim dt as new datatable
Dim xx As New MySqlDataAdapter(SqlNat, Conn)
MsgBox(Conn.State)
xx.Fill(Dt)
thanks
First of all, the if statement sholdn't be there, and if you still want to use it, then close it with an "end if" or your code won't work.
Second if that's a combobox why to state cbo.datasource = nothing??? You want to erase the content? Use cbo.clear() instead and then you can use your data table as a datasource. Also cbo.displaymember = "Dm" you need those quoutes.
If you keep your connection local to the method where it is used you can control when it is closed and disposed with a Using...End Using block. Your connection might have been disposed by another method in the class.
Public Sub Me_Sub_CboFill(ByVal Cbo As ComboBox, ByVal SqlStr As String, ByVal Dm As String, ByVal Vm As String)
Cbo.DataSource = Nothing
Dim Dt As New DataTable
Using Conn As New MySqlConnection("Your conntection string"),
xAdapter As New MySqlDataAdapter(SqlStr, Conn)
xAdapter.Fill(Dt)
End Using
Cbo.DisplayMember = Dm
Cbo.ValueMember = Vm
Cbo.DataSource = Dt
End Sub
I found the problem with my code
I put the connection string inside function that I use to check the connection and for that I needed to call the function at least one time to make everything work correctly.
thanks all for all your information
best,
Related
I am working on a project using vb and one of my forms has to display the current active reminders (reminders that haven't hit their deadline yet) into a datagridview and I have another datagridview for reminders that are past the deadline. The date and time along with reminder information is saved into my access database and I want to read the date and time from database and compare it to the system date and time and then display the reminder information.
This is how my form looks like; the top datagridview is for current reminders and the bottom one is for past/out of date reminders:
This the code for my form and what I’ve tried:
Imports System.Data.OleDb
Public Class frmReminderInfo
Private Sub frmReminderInfo_Load(sender As Object, e As EventArgs) Handles MyBase.Load
DisplayCurrentReminders()
End Sub
Private Sub DisplayCurrentReminders()
Dim ReminderDateTime As Date
Dim CurrentDateTime As Date
CurrentDateTime = Date.Now
CurrentDateTime = FormatDateTime(Date.Now, DateFormat.GeneralDate)
ReminderDateTime = FormatDateTime(ReminderDateTime, DateFormat.GeneralDate)
If DbConnect() Then
DgvCurrentReminders.Rows.Clear()
Dim SQLCmd As New OleDbCommand
With SQLCmd
.Connection = cn
.CommandText = "SELECT ReminderDate FROM TblReminder "
Dim rs As OleDbDataReader = .ExecuteReader()
While rs.Read
ReminderDateTime = (rs(0).ToString)
End While
End With
End If
cn.Close()
If CurrentDateTime = ReminderDateTime Then
Dim SQLCmd As New OleDbCommand
With SQLCmd
Dim rs As OleDbDataReader = .ExecuteReader()
While rs.Read
Dim NewStockRow As New DataGridViewRow()
NewStockRow.CreateCells(DgvCurrentReminders)
NewStockRow.SetValues({rs("ReminderID"), rs("CustomerName"), rs("DeviceInfo"), rs("RepairPrice"), rs("ReminderDate")})
NewStockRow.Tag = rs("ReminderID")
DgvCurrentReminders.Rows.Add(NewStockRow)
End While
rs.Close()
End With
End If
cn.Close()
End Sub
End Class
Disposable database objects like Connection should be declared locally in the method where they are used in a Using block.
You don't seem to have any idea of what the parts of an Sql string mean. The Select portion list the fields you want to retrieve. A star (*) in this clause means select all the fields. Here we are using a Where clause to filter the records. Only records where the field ReminderDate is greater than or equal to a parameter will be returned. This will only work if the data has been inserted properly as a DateTime.
The While loop keeps overwriting the value of ReminderDateTime on each iteration so only the last value returned by the reader will remain. Also, you are trying to force a String into a variable declared as a Date. Won't work.
Assuming the code could get beyond If CurrentDateTime = ReminderDateTime Then you would be working with a closed connection. Commands can't execute on a closed connection.
You also don't seem to have an idea how class objects work. Dim SQLCmd As New OleDbCommand Here you declare a new instance of the Command. You have no connection and no CommandText so it can't possibly be executed.
Take a look at the following code until it starts to make sense. Look up what Using blocks do. Look up the Load method of a DataTable to see what it does. Check out what a DataSource property has to offer.
Private Sub frmReminderInfo_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim dt = GetCurrentReminders()
DgvCurrentReminders.DataSource = dt
End Sub
Private Function GetCurrentReminders() As DataTable
Dim dt As New DataTable
Using cn As New OleDbConnection("Your connection string"),
SQLCmd As New OleDbCommand("SELECT * FROM TblReminder Where ReminderDate >= #Date", cn)
SQLCmd.Parameters.Add("#Date", OleDbType.Date).Value = Now
cn.Open()
Using reader = SQLCmd.ExecuteReader
dt.Load(reader)
End Using
End Using
Return dt
End Function
I am learning visual basic and OOP on the fly.
This piece of code displays the query results to a combo box on a form...
Dim c = System.Configuration.ConfigurationManager.ConnectionStrings("db").ToString()
Dim daDogs As New SqlDataAdapter ("select dog_type from humane_society with(nolock)",c)
Dim dtdogs As New DataTable
dadogs.Fill(dtdogs)
cboDogs.DataSource = dtdogs
cboDogs.DisplayMember = "dog_type"
when I change it to use a Using block, the combo box is blank
I think it is a scope issue, but I don't know how to fix it. Any productive suggestions would be greatly appreciated. Thanks!
Dim c = System.Configuration.ConfigurationManager.ConnectionStrings("db").ToString()
Using cDog As New SqlConnection(c)
Using cmd As New SqlCommand("select dog_type from humane_society with(nolock)",cDog)
cmd.CommandType = commandtype.Text
Using daDogs As New SqlDataAdapter(cmd)
Using dtDogs As New Datatable`enter code here`
daDogs.Fill(dtDogs)
' MsgBox(dtMunic.Rows(500).Field(of string)(0))
cboDogs.DataSource = dtDogs
cboDogs.DisplayMember = "dog_type"
End Using
End Using
End Using
End Using
Not sure what that message box is doing in your code. Remember that a message box halts the code until the user responds. You connection, among other things, is open while the code waits.
You are correct that items declared in a block are scoped to that block.
I have separated your database code from your user interface code.
You can save using blocks by adding a comma and combining more than one object in the block. Behaves the same, just makes the code a bit easier to read.
Private ConStr As String = System.Configuration.ConfigurationManager.ConnectionStrings("db").ToString() '"Your connection string"
Private Function GetDogData() As DataTable
Dim dt As New DataTable
Using cDog As New SqlConnection(ConStr),
cmd As New SqlCommand("select dog_type from humane_society with(nolock)", cDog)
cDog.Open()
Using reader = cmd.ExecuteReader
dt.Load(reader)
End Using
End Using
Return dt
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim dtDogs = GetDogData()
cboDogs.DataSource = dtDogs
cboDogs.DisplayMember = "dog_type"
End Sub
I have written a generic VB.net subroutine that populates a datagrid with the results of a query. The subroutine as one parameter, strQuery, which is a string that can translate to any valid SQL-Server view or stored procedure that returns a recordset. Other than that, there is no constraint on the SQL code, and sending two queries that have entirely different field profiles is a valid proposition.
In order to get this to work, I must completely purge the data grid of whatever dataset had been there previously, thus allowing the control to drop its prior identity and start over, allowing, from scratch, the new dateset to redefine the control's contents.
I have finally solved the problem. Perhaps I should have mentioned that I am using Visual Studio 2010, and that if Hersey were using a later version, then the code that worked for him may not have worked for me. The change to my code is one additional line: setting both the name and datapropertyname to the same name. I noticed that when I went to look at the column view, I noticed that the datapropertyname is how the table links to the source, and the name is an effective alias for the field, how it will be presented. Obviously, for clarity sake, both must be the same! This now works as advertised.
Thanks, ~ Peter Ferber
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim con As New SqlConnection("<Enter Connection string here>")
Dim rcd As ADODB.Recordset = ReturnRecordset("Select * From ExcludeWords")
Call DefineDataGrid("Select * From ExcludeWords")
End Sub
Private Sub btnUpdate_Click(sender As System.Object, e As System.EventArgs) Handles btnUpdate.Click
Call DefineDataGrid("Select * From FindWords")
End Sub
Sub DefineDataGrid(ByVal strQuery As String)
Dim con As New SqlConnection("<Enter Connection String Here>")
Dim dt As New DataTable
FindWordGrid.Columns.Clear()
FindWordGrid.DataSource = Nothing
Dim rcd As ADODB.Recordset = ReturnRecordset(strQuery)
Dim MyField As ADODB.Field
Dim iCount As Integer = -1
FindWordGrid.ColumnCount = rcd.Fields.Count
For Each MyField In rcd.Fields
iCount = iCount + 1
FindWordGrid.Columns(iCount).Name = MyField.Name
FindWordGrid.Columns(iCount).DataPropertyName = MyField.Name
Next
Dim cmd As New SqlCommand(strQuery, con)
Dim da = New SqlDataAdapter(cmd)
da.Fill(dt)
FindWordGrid.DataSource = dt
End Sub
Function ReturnRecordset(strQuery As String) As ADODB.Recordset
Dim con As ADODB.Connection = "<Enter Connection string here>"
ReturnRecordset = New ADODB.Recordset
ReturnRecordset.Open(strQuery, con)
End Function
My setup is easy to reproduce: I am using datasets with only a handful of records in each respective table. The only constraint is that the respective runs must have a different field profile. I have been experimenting with different techniques for much of the day, and I now think it best to get some new grey matter on the subject. Getting this process correctly is the last major hurdle for me, in making a key presentation I wish to make, in lobbying for a job role. Thanks, in advance, for your comments, thoughts, and ideas.
Sincerely, ~ Peter Ferber
So, made a couple modifications to the DefineDataGrid code you've posted. Seems to be working for me. I suspect might be something to do with the life cycle of either your cmd or con objects causing your problem. Parsing a number of different number of queries through to it and it rebuilds the datagridview correctly
Sub DefineDataGrid(ByVal strQuery As String)
Dim dt As New DataTable
FindWordGrid.DataSource = Nothing
Using con As New SqlConnection("Your Connection String Here")
Using cmd As New SqlCommand(strQuery, con)
Dim da = New SqlDataAdapter(cmd)
da.Fill(dt)
FindWordGrid.DataSource = dt
End Using
End Using
End Sub
Changed the obvious module level implementations of con and cmd to local variables, and since both types implement IDisposable, wrapped them in a Using pattern
Private Function GetSvcType(ByVal oCommand As OleDbCommand, ByVal SSTGroupID As Integer) As DataTable
Dim sSQL As New StringBuilder
sSQL.AppendLine(" Select SSTServiceTypeID AS ID, SSTServiceTypeName AS Name ")
sSQL.AppendLine(" from fgen_SSTServiceType (nolock) ")
sSQL.AppendLine(" Where 1=1 AND Disabled = 0 ")
sSQL.AppendLine(" AND fgen_SSTServiceType.SSTGroupID = #SSTGroupID ")
oCommand.Parameters.AddWithValue("#SSTGroupID", SSTGroupID)
Return GetDataTable(sSQL.ToString)
End Function
Private Function GetDataTable(ByVal SQL As String) As DataTable
Dim oConn As OleDbConnection = New OleDbConnection(_strConnection)
Dim oCommand As New OleDbCommand("", oConn)
oCommand.Connection.Open()
oCommand.CommandText = SQL
oCommand.Parameters.Clear()
Dim oDataTable As New DataTable
Dim oDataAdapter As New OleDbDataAdapter(oCommand)
oDataAdapter.Fill(oDataTable)
If oDataTable.Rows.Count > 0 Then
GetDataTable = oDataTable
Else
GetDataTable = Nothing
End If
oCommand.Connection.Close()
oCommand.Dispose()
End Function
I've been searching for hours on end and can't seem to find a solution. I need your help please thanks
I've updated my question include the GetDataTable function. Please take a look thanks.
Your command never gets the text from the StringBuilder. So I think the missing link is that you should assign the string you've built to the command text
oCommand.CommandText = sSQL.ToString()
then add the parameter after that
Private Function GetSvcType(ByVal oCommand As OleDbCommand, ByVal SSTGroupID As Integer) As DataTable
Dim sSQL As New StringBuilder()
sSQL.AppendLine(" Select SSTServiceTypeID AS ID, SSTServiceTypeName AS Name ")
sSQL.AppendLine(" from fgen_SSTServiceType (nolock) ")
sSQL.AppendLine(" Where 1=1 AND Disabled = 0 ")
sSQL.AppendLine(" AND fgen_SSTServiceType.SSTGroupID = #SSTGroupID ")
oCommand.CommandText = sSQL.ToString()
oCommand.Parameters.AddWithValue("#SSTGroupID", SSTGroupID)
Return GetDataTable(oCommand.CommandText)
End Function
Alternatively, you may want to use a Using to create a command and dispose it. I'd write it but I don't see your connection so you should look into this answer for an example.
I wasn't sure what the Where 1 = 1 and the (no lock) were doing so I removed them.
The function FillDataTable contains all your database access code which keeps it separate from the User Interface code. Your database objects should be locale so you can control that they are closed and disposed. The Using...End Using block takes care of this even if there is an error. Get rid of any class level variables for commands and connections. Both the command and connection are included; note the comma at the end if the first line of the Using.
You can pass your connection string directly to the constructor of the connection and pass the command text and connection directly to the constructor of the command. Saves having to set these properties individually.
OleDb pays no attention to the name of the parameter, so, the order that the parameter is added to the Parameters collection must match the order that the parameter appears in the command text. In this case, you have only one but just for future reference. It is better to use the Parameters.Add() which includes the database datatype. See http://www.dbdelta.com/addwithvalue-is-evil/
and
https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/
and another one:
https://dba.stackexchange.com/questions/195937/addwithvalue-performance-and-plan-cache-implications
Here is another
https://andrevdm.blogspot.com/2010/12/parameterised-queriesdont-use.html
Note: I had to guess at the datatype of your parameter. Check your database for the actual type.
Always open your connection at the last possible moment (the line before the .Execute...) and close it as soon as possible (the End Using)
Private Function FillDataTable(GroupID As Long) As DataTable
Dim strSQL = "Select SSTServiceTypeID AS ID, SSTServiceTypeName As Name
From fgen_SSTServiceType
Where Disabled = 0
And SSTGroupID = #SSTGroupID "
Dim dt As New DataTable
Using cn As New OleDbConnection("Your connection string"),
cmd As New OleDbCommand(strSQL, cn)
cmd.Parameters.Add("#SSTGroupID", OleDbType.BigInt).Value = GroupID
cn.Open()
dt.Load(cmd.ExecuteReader)
End Using
Return dt
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim dt = FillDataTable(7L) 'the L indicates that this is a long,pass the GroupID to the function
DataGridView1.DataSource = dt
End Sub
EDIT
Dim dt = FillDataTable(7L) 'In the button code
And in the Data Access code change Oledb to Sql
Imports System.Data.SqlClient
Class DataAccess
Private Function FillDataTable(GroupID As Long) As DataTable
Dim strSQL = "Select SSTServiceTypeID AS ID, SSTServiceTypeName As Name
From fgen_SSTServiceType
Where Disabled = 0
And SSTGroupID = #SSTGroupID "
Dim dt As New DataTable
Using cn As New SqlConnection("Your connection string"),
cmd As New SqlCommand(strSQL, cn)
cmd.Parameters.Add("#SSTGroupID", SqlDbType.BigInt).Value = GroupID
cn.Open()
dt.Load(cmd.ExecuteReader)
End Using
Return dt
End Function
End Class
I know that I should always dispose DataAdapter instances. In most cases I'm disposing it immediately after closing the connection, but in cases like when user will be modifying DataTable items (displayed in ListBox or DataGridView) I create the DataAdapter, use it to fill the DataTable, but don't dispose it until the user clickes Save which calls DataAdapter.Update(DataTable)... not my main question but is this the right approach?
Back to the main question, I have these two functions:
Public Function LoadCompaniesDT(ByRef dtCompanies As DataTable) As Boolean
Using daCompanies As MySqlDataAdapter = Nothing
Return LoadCompaniesDT(daCompanies, dtCompanies)
End Using
End Function
Public Function LoadCompaniesDT(ByRef daCompanies As MySqlDataAdapter, ByRef dtCompanies As DataTable) As Boolean
Dim sql As String = "SELECT * FROM companies"
Return LoadDT(daCompanies, dtCompanies, sql, Res.CompaniesFailedMsgBody)
End Function
They're used to call LoadDT which fills the DataTable so I have the choice to pass a DataAdapter or not.
Now I'm confused about something: When using the first LoadCompaniesDT function, daCompanies is disposed before reaching End Using.. like this:
Public Function LoadCompaniesDT(ByRef dtCompanies As DataTable) As Boolean
Using daCompanies As MySqlDataAdapter = Nothing
Dim tmp As Boolean = LoadCompaniesDT(daCompanies, dtCompanies)
Console.WriteLine(daCompanies Is Nothing) ' ==> True!!
Return tmp
End Using
End Function
Note: if I use Dim daCompanies instead of Using daCompanies then daCompanies Is Nothing will return False.
LoadDT function code:
Private Function LoadDT(ByRef da As MySqlDataAdapter, ByRef dt As DataTable,
ByVal sqlQuery As String,
ByVal errorText As String) As Boolean
Dim connStr As String = String.Format("server={0}; port={1}; user id={2}; password={3}; database={4}",
DbServer, DbServerPort, DbUserName, DbPassword, DatabaseName)
Dim conn As MySqlConnection = New MySqlConnection(connStr)
Dim cmd As MySqlCommand = New MySqlCommand
Try
conn.Open()
cmd.CommandType = CommandType.Text
cmd.CommandText = sqlQuery
cmd.Connection = conn
da = New MySqlDataAdapter(cmd)
dt = New DataTable
da.Fill(dt)
Return True
Catch ex As Exception
MessageBox.Show(errorText, Res.ServerError, MessageBoxButtons.OK, MessageBoxIcon.Error)
Return False
Finally
cmd.Dispose()
cmd = Nothing
conn.Close()
conn.Dispose()
End Try
End Function
Update: you're right, you don't get an initialized MySqlDataAdapter back from the methods if the ByRef passed instance is used in a Using-statement. Those variables are readonly. In C# you get this meaningful compiler error:
Error CS1657 Cannot pass 'daCompanies' as a ref or out argument
because it is a 'using variable'
It's documented here:
Compiler Error CS1657
Cannot pass 'parameter' as a ref or out argument because 'reason''
This error occurs when a variable is passed as a ref or out argument
in a context in which that variable is readonly. Readonly contexts
include foreach iteration variables, using variables, and fixed
variables.
In VB.NET you can do that(so the compiler ignores it which is almost a bug) but the variable is not initialized afterwards. But as mentioned below, you should not use this approach anyway.
According to the the other question:
If you look at the sample on MSDN you'll see that microsoft also doesn't dispose the dataadapter. So it not really necessary. Having said that, it's always best practise to use the Using statement on anything that implements IDisposable.
A DataAdapter is not an expensive object and it does not hold unmanaged resources(like the connection). So it doesn't hurt to create a new instance from it whereever you need one. And you don't need to dispose it, but that's an implementation detail that might change in future or in a different implementation of DbDataAdapter, so it's still best practise to dispose it, best by using the Using-statement.
I wouldn't use your approach because you are passing the sql-string to the method which often leads to a sql injection vulnerabiliy. Instead use sql parameters.
For example:
Private Function LoadDT() As DataTable
Dim tbl As New DataTable()
'Load connection string from app.config or web.config
Dim sql As String = "SELECT * FROM companies" ' don't use * but list all columns explicitely
Using conn As New MySqlConnection(My.Settings.MySqlConnection)
Using da = New MySqlDataAdapter(sql, conn)
da.Fill(tbl)
End Using
End Using
Return tbl
End Function
Instead of passing an errorText ByRef i'd use a logging framework like log4net.