already an open DataReader associated with this Command when am chekcing with invalid data - vb.net

Private Sub txt_sname_GotFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles txt_sname.GotFocus
Dim fcs As String
fcs = "select fname,dept from nstudent where stid = '" & txt_sid.Text & "'"
scmd1 = New SqlCommand(fcs, con)
dr1 = scmd1.ExecuteReader
If dr1.HasRows Then
Do While (dr1.Read)
txt_sname.Text = dr1.Item(0)
cmb_dept.Text = dr1.Item(1)
Loop
Else
MsgBox("Not Found")
End If
scmd1.Dispose()
If Not dr1.IsClosed Then dr1.Close()
End Sub
The above code for data from database and pass to textbox. When am running the program and checking with data which already present in database, it working properly. but checking with some other data(which not present in db)following error is occurring and exiting.
error:
"There is already an open DataReader associated with this Command which must be closed first."
pls help me..

Some observations:
Instead of using a global command object, use a local one. Especially since you are creating a new command anyway. And it looks like this is true of the dr1 as well.
You are not preventing SQL injection, so someone can type text in txt_sid that causes security issues by deleting data, dropping tables, or getting access to other data in the database.
You are looping and setting the same variables multiple times. If there is only going to be one record, don't bother looping.
Wrap the entire thing around a try/catch
Private Sub txt_sname_GotFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles txt_sname.GotFocus
Dim cmd1 As SqlCommand = New SqlCommand(fcs, "select fname,dept from nstudent where stid = #stid")
cmd1.Parameters.Parameters.AddWithValue("#stid", txt_sid.Text)
Dim studentReader as SqlDataReader
Try
studentReader = scmd1.ExecuteReader
If studentReader.Read Then
txt_sname.Text = studentReader.Item(0)
cmb_dept.Text = studentReader.Item(1)
Else
MsgBox("Not Found")
End If
Finally
studentReader.Close()
cmd1.Dispose()
End Try
End Sub
Finally, I think you might want to actually do this when txt_sid changes, not when txt_sname gets focus.

Related

Bound datatgridview not saving changes to tableadapter, and thus not to database

I have a datagridview bound to a table that seems to work fine, but will not save changes to the database. I am using the same code as I used in another project, which did save changes, so I am flummoxed. Also, I have debugged and the save code is being called, just not working. The code in the form calls a separate class with business logic.
Code in the form below:
Private Sub frmAdminAssign_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' Below is generated by Visual Studio when I select my data source for my datagridview
Credit_adminTableAdapter.Fill(DataSet1.credit_admin) ' Foreign key relationship to table being updated (lookup table).
LoanTableAdapter.Fill(DataSet1.loan) ' Table being updated
admin_assign = New AdminAssign()
admin_assign.FilterUnassigned(dgvAssign, LoanTableAdapter)
End Sub
Private Sub dgvAssign_CellEndEdit(sender As Object, e As DataGridViewCellEventArgs) Handles dgvAssign.CellEndEdit
admin_assign.SaveAssignmentChanges(DataSet1, LoanTableAdapter)
End Sub
Below is code in business logic class called from above:
Public Sub SaveAssignmentChanges(ByRef data_set As DataSet1, ByRef loan_table_adapter As DataSet1TableAdapters.loanTableAdapter)
' Saves changes to DB.
Dim cmdBuilder As SqlCommandBuilder
cmdBuilder = New SqlCommandBuilder(loan_table_adapter.Adapter)
loan_table_adapter.Adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(True)
Try
loan_table_adapter.Adapter.Update(data_set)
Catch unknown_ex As Exception
Dim error_title As String = "Database Save Error"
Dim unknown_error As String = $"There was an error saving to the database.{vbNewLine}Error: {unknown_ex.ToString}"
MessageBox.Show(unknown_error, error_title, MessageBoxButtons.OK, MessageBoxIcon.Warning)
End Try
End Sub
The data from the datagridview is not saving to the tableadapter, which I found out by adding the lines below in the start of the second procedure:
Dim x As String = loan_table_adapter.GetData()(0)("note_number") ' Unchanged, check was right row
Dim y As String = loan_table_adapter.GetData()(0)("credit_admin_id") ' Changed in datagridview but still null (get null error)
I figured it out, it was related to a filter routine that is not above where I passed a tableadapter byref when I should have passed a datatable byval. So, related to one of jmcilhinney's byref vs byval comments above. See problem code below:
Public Sub FilterUnassigned(ByRef dgv_assigned As DataGridView, loan_table_adapter As DataSet1TableAdapters.loanTableAdapter)
' Should have passed in DataSet1.loan DataTable ByVal and used it below:
Dim unassigned_loans As DataView = loan_table_adapter.GetData().DefaultView()
unassigned_loans.RowFilter = "request_date Is Not Null AND numerated_assigned_user Is Null"
dgv_assigned.DataSource = unassigned_loans
End Sub
Also, skipping the SQLCommandBuilder commands and my code still works. So, all I need is one line as per jmcilhinney instead of 4 (excl error handling) as per below:
Try
loan_table_adapter.Update(data_set)
Catch unknown_ex As Exception
Dim error_title As String = "Database Save Error"
Dim unknown_error As String = $"There was an error saving to the database. Please contact Kevin Strickler to resolve.{vbNewLine}Error: {unknown_ex.ToString}"
MessageBox.Show(unknown_error, error_title, MessageBoxButtons.OK, MessageBoxIcon.Warning)
End Try

SELECTing data from database in vb and outputting data into label

I have the following code which SELECTs data from a database and outputs a value to a label on the form:
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim strConn As String = System.Configuration.ConfigurationManager.ConnectionStrings("yourConnectionString").ToString()
Dim sql As String = "SELECT aid FROM tbl_RAPA WHERE username=#username"
Dim conn As New Data.SqlClient.SqlConnection(strConn)
Dim objDR As Data.SqlClient.SqlDataReader
Dim Cmd As New Data.SqlClient.SqlCommand(sql, conn)
Cmd.Parameters.AddWithValue("#username", User.Identity.Name)
conn.Open()
objDR = Cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection)
While objDR.Read()
Label1.Text = objDR("aid")
End While
End Sub
However, if the value in the database is empty, the program runs into an error. Is there a way for me to do this so the program just returns an empty value rather than crashing?
The error message i am given is System.InvalidCastException: 'Unable to cast object of type 'System.DBNull' to type 'System.Windows.Forms.Label'.' on the line Label1.Text = objDR("aid")
Database objects generally need to be closed and disposed. Using...End Using blocks will do this for you even if there is an error.
Since you are only expecting one piece of data you can use .ExecuteScalar which provides the first column of the first row of the result set. This method returns an object.
Try to always use the the .Add method with Parameters. 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
I had to guess at the database type so, check your database for the real value.
Don't update the User Interface until after the connection is closed and diposed. (End Using). I declared aid before the Using block so, it could be used after the block. Check if the object, aid, is not Nothing before adding it to the label's Text.
Imports MySql.Data.MySqlClient
Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim aid As Object
Using conn As New MySqlConnection(ConfigurationManager.ConnectionStrings("yourConnectionString").ToString),
cmd As New MySqlCommand("SELECT aid FROM tbl_RAPA WHERE username=#username", conn)
cmd.Parameters.Add("#username", MySqlDbType.VarChar).Value = User.Identity.Name
aid = cmd.ExecuteScalar
conn.Open()
End Using
If Not IsNothing(aid) Then
Label1.Text = aid.ToString
End If
End Sub
I would add this before While objDR.Read() as a precaution in case your query returns no rows:
if objDR.HasRows
...
Then, to handle the null values (this is probably what you mean by empty):
If Not String.IsNullOrEmpty(objDR.Item("aid")) Then
Label1.Text = objDR("aid")
Else
Label1.Text = "Null !"
End if
You could also use ExecuteScalar() if you are only expecting one record. But you would need to handle the situation where no matching record is found.

Update From DataGridView to Database

Dim sb As New MySqlConnectionStringBuilder
sb.Server = Form1.hostname.Text
sb.UserID = Form1.rootuser.Text
sb.Password = Form1.rootpassword.Text
sb.Database = Form1.hostdb.Text
sb.Port = Form1.hostport.Text
Using connection As New MySqlConnection(sb.ConnectionString)
Try
connection.Open()
Dim adapter1 As New MySqlDataAdapter(TextBox1.Text, connection)
Dim cmdb1 = New MySqlCommandBuilder(adapter1)
Dim ds As New DataSet
Dim words As String() = TextBox1.Text.Split(New Char() {" "c})
Dim tablenamewords = (words(3))
adapter1.Update(ds, tablenamewords)
Catch ex As MySqlException
MessageBox.Show(ex.Message)
Finally
connection.Dispose()
End Try
End Using
It's giving me this error:
Update unable to find TableMapping['creature_template'] or DataTable 'creature_template'.
I want to select items then use a DataGrid to update them.
Note: tablenamewords = "creature_template"
You can solve this in alot of different methods.
I prefer to update the DB with a single query every time the the user ends editing a cell.
BUT FIRST you have to bind a source to your DataGridView!
How to bind a source:
Go to your application design an click on your DataGridView
Click on the pause/start like little button as shown:
Click on the "ComboBox" like textbox labeled as "chose data source" as shown and click on it:
It will open a "form", click on the Add data source as shown:
Follow the wizard, you can do it without any furhter explanation!
Well done, you almost completed it. Just go in your code, and look for this sub:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.your_table_nameTableAdapter.Fill(Me.your_db_nameDataSet.your_table_name)
End Sub
The line inside your Form1_Load Sub will upload the data inside your DataGridView, leave it there if you want it to load with the form or cut/paste it wherever you like.
Now you can add this the code to programmatically update the DB:
Private Sub DataGridView1_CellEndEdit(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles DataGridView1.CellEndEdit
Try
Dim location As Point = DataGridView1.CurrentCellAddress
Dim sqlCmd As String = "UPDATE table_name SET " & _
DataGridView1.Columns(location.X).Name.Replace("DataGridViewTextBoxColumn", "") & " = #Data " & _
"WHERE " & _
DataGridView1.Columns(0).Name.Replace("DataGridViewTextBoxColumn", "") & " = #ID"
Using myConn As New SqlConnection(My.Settings.VISURE_DA_PDFConnectionString)
myConn.Open()
Using myCmd As New SqlCommand(sqlCmd, myConn)
myCmd.Parameters.Add("#Data", SqlDbType.VarChar)
myCmd.Parameters.Add("#ID", SqlDbType.Int)
myCmd.Parameters("#Data").Value = DataGridView1.Rows(location.Y).Cells(location.X).Value
myCmd.Parameters("#ID").Value = DataGridView1.Rows(0).Cells(location.X).Value
myCmd.ExecuteNonQuery()
End Using
myConn.Close()
End Using
Catch ex As Exception
Err.Clear()
End Try
End Sub
Remarks:
location.X is the col index, location.Y is the row index
DataGridView1.Columns(0).Name This is my ID (primary key) and it is always in the first column. Change it with yours.
Don't forget to add .Replace("DataGridViewTextBoxColumn", "") whan you are getting your column name
As previously said, this code will update your DB every time the user edits one cell on your DataGridView updating only the edited cell. This is not a big issue because if you want to edit two ore more cell, every time you leave an edited cell the method DataGridView1_CellEndEdit is fired!

how do I refresh the textbox/datatable to re-run this command?

This piece of code grabs a customers hire record using their hire ID and displays their details in multiple textboxes. It all works fine and well, however, I can only run it once. If I type in another customers hire record ID it just displays the first customers details that were materialised, which I assume is because the datatable has been populated and not refreshed based on the new hire record ID I've entered.
Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click
Dim hirerecord1 As Integer = TextBox13.Text
Dim cmd2 As New SqlCommand("SELECT * FROM HireItemRecord WHERE HireRecord_Id = " & hirerecord1, cnn)
Dim sqlDa As New SqlDataAdapter(cmd2)
sqlDa.Fill(dt1)
If dt1.Rows.Count > 0 Then
TextBox14.Text = dt1.Rows(0)("RentalItem_Id").ToString()
TextBox15.Text = dt1.Rows(0)("HireRecord_Id").ToString()
TextBox45.Text = dt1.Rows(0)("HireItemBeginDate").ToString()
End If
cnn.Close()
End Sub
I'm not quite sure what to do to fix this...
Also, I'm having a similar problem with this..
Private Sub TextBox46_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox46.TextChanged
Dim keywords2 As String = TextBox46.Text
ds1.Tables("PersonDetails").DefaultView.RowFilter = "Last_Name like '%" & keywords2 & "%' "
End Sub
With this I can search a column of a datagridview for a match. It works all fine and well, until I insert a new record into the database at which point I refresh the datagridview to display the newly added record. After I do this, I can no longer search using the textbox. Once again, I'm not quite sure what to do to fix this issue.
Thank you very much for your help.
You're closing your connection at the end of the Button6_Click method and I can't see where it gets opened again. That would fit with your symptoms of the method only running successfully once. Have you tried to step through the code to see where it fails the second time?

Random browsing of images (animated)

I was trying to make a program that shows picture in a folder.
The path of each picture was stored in the database.
My problem was it only show the last images stored in the database and not all the pictures.
The code is:
Private Sub Form3_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Timer1.Enabled = True
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Try
Dim ctr As Integer = 0
Dim pic As String
Dim sqlconn As New SqlConnection("data source=NMPI_2;initial catalog=IPCS; " & _
"password=rhyatco; " & _
"persist security info=True; " & _
"user id= rhyatco;" & _
"packet size=4096")
sqlconn.Open()
Dim query As String = "Select picture from Bpicture"
Dim sqlcomm As New SqlCommand(query, sqlconn)
Dim reader As SqlDataReader
reader = sqlcomm.ExecuteReader
While reader.Read
pic = reader("picture").ToString
Me.PictureBox1.Image = Image.FromFile(pic)
Me.PictureBox1.SizeMode = PictureBoxSizeMode.StretchImage
End While
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
It's always going to show the last image, because you are retrieving the entire table and retrieving all the records with the While loop. The last image will always be the winner.
There are two possible solutions:
One, randomly retrieve one image from the database. Check this stackoverflow thread on how to randomly select a row. The caveat with this solution is you make a call to the database every time.
Two, retrieve all the images from the database, store the data in a collection and randomly select one of the images from the collection. The caveat with this solution is there maybe too many images to store in a collection in memory, in which case you have to go with One.
Well the problem that I see with what you've provided is that you're running the query and iterating through all the rows in the result set every time the timer fires.
I think what you're really looking for is to download the result set once when the form loads, and then to switch which picture you're looking at on the timer.
One way to just rotate through them all over and over again would be to download the list and populate them into a Queue of filenames, and then on the timer just do something like this (sorry, my example is in C#):
string fileName = imageFileNameQueue.Dequeue();
PictureBox1.Image = Image.FromFile(fileName);
imageFileNameQueue.Enqueue (fileName); // Put the file back at the back of the queue
A simple change to your existing solution that will kind of work is to break out of the while loop with some probability. A problem with this solution is that images later in the query result are much less likely to be shown than earlier images.
I haven't done any VB so I'm coding via Google, but you'll need to create an instance of Random somewhere:
Dim rand as new Random()
Then in your while loop, pull off a random number to see if you should stop:
While reader.Read
...
If rand.Next(10) > 8 Then
Exit While
End If
End While
EDIT: You should also move the code to set the Image and SizeMode out of the while loop so that they're only set one time, once you've decided on the image.