VB.net update SQL command - sql

For some reason I cannot get my record to update. Here goes:
I have a Windows form, that will upon loading display the current status of an aircraft. If that status changes I can select the value from a combobox (cboServ). When I click the Exit button from that form the status should update, so upon loading the form again the new value appears. However it doesn't.
This is what I have currently as the code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim updateQuery As String = "UPDATE dbo.aircraft SET dbo.aircraft.condition = #OE_status WHERE aircraft.avNum = 'ab201'"
Dim updateCmd As New SqlCommand(updateQuery, con)
updateCmd.Parameters.Add("#OE_status", cboServ.SelectedValue)
MessageBox.Show("it worked")
con.Close()
Me.Close()
Form1.Show()
End Sub
The error I get back is - which I don't fully understand:
System.InvalidCastException: 'Conversion from type 'DataRowView' to type 'String' is not valid.'
This comes from the line reading cboServ.SelectedValue.

There is more than one problem with the code as shown.
First, you should use Option Strict On to make sure that the types of all the variables used match up properly.
You can isolate the problem more easily by separating the things being done in the button click handler out into other methods. For example, we could put the database interaction into its own method:
Sub UpdateAircraftCondition(avNum As String, condition As String)
Dim connStr = "YOUR CONNECTION STRING HERE"
Dim updateQuery As String = "UPDATE dbo.aircraft SET dbo.aircraft.condition = #OE_status WHERE aircraft.avNum = #AvNum"
Using conn As New SqlConnection(connStr)
Using cmd As New SqlCommand(updateQuery, conn)
cmd.Parameters.Add(New SqlParameter With {.ParameterName = "#AvNum", .SqlDbType = SqlDbType.NVarChar, .Size = 16, .Value = avNum})
cmd.Parameters.Add(New SqlParameter With {.ParameterName = "#OE_Status", .SqlDbType = SqlDbType.NVarChar, .Size = 16, .Value = condition})
conn.Open()
cmd.ExecuteNonQuery()
conn.Close()
End Using
End Using
End Sub
(You will need to make the Size (for string data) and SqlDbType match up to the columns in the database.)
Notice how the connection only exists where it is needed - the Using construct takes care of disposing of unmanaged resources (like the SqlConnection) to keep memory tidied up and not leaking.
Instead of putting the actual connection string in everywhere, which makes it a lot of hassle to change it, it is normal to put it in some settings file, for example by using My.Settings.
According to the error message, cboServ.SelectedValue is a DataRowView, not a String. Which makes me wonder if cboServ is not a ComboBox. But we can get a useful error message by trying the following code for the button click event handler:
Private Sub bnUpdateCondition_Click(sender As Object, e As EventArgs) Handles bnUpdateCondition.Click
If cboServ.SelectedIndex >= 0 Then
Dim aircraftCondition = cboServ.SelectedItem.ToString()
Dim avNum = "ab201"
UpdateAircraftCondition(avNum, aircraftCondition)
' more code may go here
End If
End Sub
Please note that I tried to give the button a meaningful name: doing that will make it much easier for you in the future than trying to figure out what "Button337" is for.

Change the row:
updateCmd.Parameters.Add("#OE_status", cboServ.SelectedValue)
to:
updateCmd.Parameters.AddWithValue("#OE_status", cboServ.SelectedValue.ToString())
or you can use a explicit cast:
var dataRowView = cboServ.SelectedValue as DataRowView;
var row = dataRowView.row As MyDataTableDefinition;
updateCmd.Parameters.AddWithValue("#OE_status", row[“OE_status”].ToString())

Related

Using block - scope issues

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

ComboBox.SelectedText Property and Database Error

This specific code ComboBox2.SelectedItem query has an error to my database. I think I'm missing something with this code ComboBox2.SelectedItem:
Private Sub UpdateCombo()
ComboBox2.Items.Clear()
SQLcon.Open()
Dim Command As SqlClient.SqlCommand = SQLcon.CreateCommand()
Command.CommandText = "Select productName From tblProductsStocks"
Dim SQLReader As SqlClient.SqlDataReader = Command.ExecuteReader()
While SQLReader.Read()
ComboBox2.Items.Add(SQLReader.Item("productName"))
End While
SQLcon.Close()
End Sub
Private Sub ComboBox2_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox2.SelectedIndexChanged
SQLcon.Open()
Dim Command As SqlClient.SqlCommand = SQLcon.CreateCommand()
Command.CommandText = "Select * From tblProductsStocks WHERE productName=" & ComboBox2.SelectedItem
Dim SQLReader As SqlClient.SqlDataReader = Command.ExecuteReader()
SQLReader.Read()
TextBox1.Text = SQLReader.Item("productType")
TextBox2.Text = SQLReader.Item("productMass")
SQLcon.Close()
End Sub
Please turn on Option Strict. This is a 2 part process. First for the current project - In Solution Explorer double click My Project. Choose Compile on the left. In the Option Strict drop-down select ON. Second for future projects - Go to the Tools Menu -> Options -> Projects and Solutions -> VB Defaults. In the Option Strict drop-down select ON. This will save you from bugs at runtime.
Connections need to be disposed as well as closed to be returned to the connection pool. If there is an error, your code may not even close the connection. If you keep your database objects local, you can control that they are closed and disposed. Using...End Using blocks take care of this for you even if there is an error. In my code the Command is part of the Using block. Note the comma after the connection constructor.
You can pass the connection string directly to the constructor of the connection. Likewise pass the command text and the connection to the command constructor.
Use parameters. Not only does it avoids errors concatenating strings but it also avoids Sql injection. In your code, the selected item is meant to be a string but you have failed to add the surrounding single quotes. This is not needed when you use parameters. Command text is executable code to the server and a malicious user can enter things that would ruin you database. Parameters are considered as values by the server, not executable code so they are much safer.
Open the connection at the last possible moment, right before the .Execute... Connections are precious resources and need to be opened, closed and disposed as quickly as possible. The connection must be open as long as the reader is engaged. So I moved updating the user interface (the text boxes) to outside the using block.
Private Sub ComboBox2_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox2.SelectedIndexChanged
Dim String1 As String = ""
Dim String2 As String = ""
Using SQLcon As New SqlConnection("Your connection string"),
Command As New SqlCommand("Select * From tblProductsStocks WHERE productName= #producName", SQLcon)
'Check your database for the actual datatype and field size
Command.Parameters.Add("#productName", SqlDbType.VarChar, 100).Value = ComboBox2.SelectedItem.ToString
SQLcon.Open()
Dim SQLReader As SqlClient.SqlDataReader = Command.ExecuteReader()
SQLReader.Read()
String1 = SQLReader.Item("productType").ToString
String2 = SQLReader.Item("productMass").ToString
End Using 'closes and disposes the connection and command
TextBox1.Text = String1
TextBox2.Text = String2
End Sub

DataGridView stay empty even if SQL request is right

So, I simply need to fill a DataGridView of a WinForm with the result of a SQL request that I tried in MySQL Workbench and works perfectly.
It's not the first time I'm doing this in this particular program and all the others worked fine. However, no matter what I do, the DataGridView stays empty.
Here goes my code :
Dim BindingName As New BindingSource, ProdSet As New DataTable
Private Sub SelectAllFacture()
ClassConfig.Connexion.Open()
ProdSet.Clear()
Dim Requete As String
Requete = "SELECT * FROM Product"
Try
Dim Cmd As New MySqlCommand
With Cmd
.Connection = ClassConfig.Connexion
.CommandText = Requete
End With
Dim Adpt As New MySqlDataAdapter(Cmd)
Adpt.Fill(ProdSet)
Catch ex As Exception
Autorisations.ErrorCheck(ex)
End Try
ClassConfig.Connexion.Close()
BindingName.DataSource = ProdSet
DataGridView.DataSource = BindingName
End Sub
To explicit what is not shown :
DataGridView is ... well, the DataGridView
Autorisations.ErrorCheck(ex) calls a Sub from another class that opens a MsgBox on error (It doesn't).
ClassConfig.Connexion is simply the connection, stored in another Class
Instead of Filling a Dataset (prodset), make it a datatable
Dim prdtable as new DataTable
then
Adpt.Fill(prdtable)
BindingName.DataSource = prdtable
Also make sure you have either already added the correct columns to the DataGridView or set:
dataGridView.AutoGenerateColumns = true

How can I use multiple connection in If else code blocks?

I want to use multiple connection in if else code block but it's giving the following error when I checked in a Messagebox :
"Argument prompt cannot be converted to string"
Here is my code :
Try
Conn.Open()
Com.CommandText = "Select * FROM Table1 WHERE ID=" & txtID.Text & " AND DOR=#01/01/1900# AND Paid = '0' ORDER BY DOI"
Dr = Com.ExecuteReader
If Dr.Read = True Then
txtInstNo.Text = Dr(2)
txtInstAmount.Text = Dr(4)
Else
If MsgBox("Wait! You're not allowed to do it. Do you still want to continue ? ", MsgBoxStyle.YesNo Or MsgBoxStyle.Question, "Alert") = MsgBoxResult.Yes Then
Try
Dim Con As New OleDbConnection
Dim Comm As New OleDbCommand
Dim Dr2 As OleDbDataReader
Con.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Database.accdb"
Con.Open()
Comm.Connection = Con
Comm.CommandText = "Select * FROM Table1 WHERE ID=" & txtID.Text & " AND DOR=#01/01/1900# AND Paid = '0' ORDER BY DOI"
Comm.CommandType = CommandType.Text
Dr2 = Comm.ExecuteReader
MsgBox(Dr2) <-- Here I got that error
If Dr.Read = True Then
txtInstNo.Text = Dr(2)
txtInstAmount.Text = Dr(4)
Else
MsgBox("Sorry, no record found",MsgBoxStyle.Exclamation, "Alert")
End If
Dr2.Close()
Con.Close()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End If
End If
Dr.Close()
Conn.Close()
Catch ex As Exception
MsgBox(ex.Message)
End Try
Your query might be returning entire rows, objects, widgets or whatever. As #Andrew Morton pointed out, it's a Data Reader. It's not going to implicitly convert your result. You'll have to manipulate your reader result and convert it to a string to do anything useful. You'll also have to handle if your DataReader returns a null result, which when converted should be "" an empty string.
If you just want to see what Dr2 contains, you could try MsgBox(CStr(Dr2)). No error handling, if it throws an exception.
There are a few things which could be modified in your code to make it a bit shorter and so easier to track down what is not working. It is easier to isolate a problem if you have the minimal amount of code which shows the problem: How to create a Minimal, Complete, and Verifiable example.
I'll show my suggestion for a minimal amount of code on a new form which should help you narrow down where the problem is, and then I'll go over why I have written it that way and what could be going wrong.
Option Strict On
Option Infer On
Imports System.Data.OleDb
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim connStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Database.accdb"
Dim sql = "SELECT InstNo, InstAmount FROM Table1 WHERE ID = ? AND DOR = #01/01/1900# AND PAID='0' ORDER BY DOI"
' lists to hold the retrieved data
Dim instNos As New List(Of String)
Dim instAmounts As New List(Of String)
' get the data
Using conn As New OleDbConnection(connStr)
Using cmd As New OleDbCommand(sql, conn)
cmd.Parameters.Add(New OleDbParameter With {.ParameterName = "#ID",
.DbType = DbType.String,
.Value = txtID.Text})
conn.Open()
Dim rdr = cmd.ExecuteReader()
While rdr.Read
instNos.Add(rdr.GetString(0))
instAmounts.Add(rdr.GetString(1))
End While
End Using
End Using
' act on the data
If instNos.Count = 0 Then
MsgBox(String.Format("No records were found for ID ""{0}"".", txtID.Text))
Else
txtInstNo.Text = String.Join(vbCrLf, instNos)
txtInstAmount.Text = String.Join(vbCrLf, instAmounts)
End If
End Sub
End Class
The code.
I start with Option Strict On to make sure that all data types match up and I haven't done anything else silly which Visual Studio can point out to me.
I use Option Infer On so that I don't have to type out the type of variables when the compiler can infer what they are.
I used just one Button on the form along with the three named textboxes as we are going for minimal code.
I set up the two strings which are going to be used in one place at the top of the sub because it is easier to maintain the code that way. Normally, you would declare variables just before they are used to minimise their scope.
I specified exactly which columns I want from the database. There is no point retrieving all of them with *. I had to guess what the columns are called - you will need to put in the actual names if they are different.
The result of a query to the database might have more than one record, so I initialise Lists for the data. (Your query has an ORDER BY so I assume that there could be more than one record.)
The Using statement makes sure that resources are released cleanly whatever else happens.
For OleDb, parameters are normally represented by a ?. (If there is more than one, they are all represented by question marks and the parameters must be added in the order in which they are to be put into the query.) I had to guess at the data type for the ID column - please put in the correct type. When you create the parameter, you can still use a meaningful name for it, even though it is ignored by the computer.
Next, the data (if any) is read. I do nothing else at this point except read the data to keep it fast and tidy. I assumed that the data to be retrieved is strings, hence the GetStrings. You should adjust that if required, and also the types of the Lists to match.
Now that the data has been read, I act on it. If there was no data, show an appropriate message, and if there was data then I put it into multiline textboxes to show it. Note that I wrote multiline: if it was a single line textbox then only the last line would be visible. Other ways of displaying it could be more useful, for example a DataGridView - in which case I might have read the data into a DataTable or a list of some class.
What could go wrong.
In your query, you have AND DOR = #01/01/1900# - is this correct?
In your query, you have AND PAID = '0' - is PAID actually a string? If it is a number then it should be AND PAID = 0. (The DB should convert the string "0" to the number zero, but why make it do extra work?)
Now that you have multiline textboxes for the results, you can see if it just happens that the last records found happened to be blank, and are simply not visible in a single-line textbox.
Finally, are you sure it is using the correct database file?

Editing SQL data

Yeah this is a pretty simple topic, but my form just isn't updating the row in the database:
Here's my code:
Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSubmit.Click
If Request.QueryString("a") = "edit" Then
Dim CategoryText As String = lstCategory.SelectedItem.Text
Dim conn As New SqlConnection("Data Source=.\SQLEXPRESS; Initial Catalog=LogansArchive; Integrated Security=True;")
Dim cmd As New SqlCommand("UpdateArticle", conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("#ArticleID", Request.QueryString("i"))
cmd.Parameters.AddWithValue("#Category", CategoryText)
cmd.Parameters.AddWithValue("#ArticleTitle", txtNewTitle.Text)
cmd.Parameters.AddWithValue("#ArticleContent", txtContent.Text)
conn.Open()
cmd.ExecuteNonQuery()
conn.Close()
Else
Dim CategoryText As String = lstCategory.SelectedItem.Text
Dim conn As New SqlConnection("Data Source=.\SQLEXPRESS; Initial Catalog=LogansArchive; Integrated Security=True;")
Dim cmd As New SqlCommand("AddArticle", conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("#Category", CategoryText)
cmd.Parameters.AddWithValue("#Date", Now)
cmd.Parameters.AddWithValue("#ArticleTitle", txtNewTitle.Text)
cmd.Parameters.AddWithValue("#ArticleContent", txtContent.Text)
conn.Open()
cmd.ExecuteNonQuery()
conn.Close()
End If
End Sub
Here's the SP that the code refers to:
ALTER PROCEDURE [dbo].[UpdateArticle]
-- Add the parameters for the stored procedure here
#ArticleID int,
#Category varchar(20),
#ArticleTitle varchar(100),
#ArticleContent text
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
UPDATE Articles
SET
Articles.Category = (SELECT CategoryID FROM Categories WHERE CategoryName = #Category),
Articles.ArticleTitle = #ArticleTitle,
Articles.ArticleContent = #ArticleContent
WHERE Articles.ArticleID = #ArticleID
END
I've never used nested SELECTs in SQL before so I'm not sure if I did it right.
Aside from that, this is all stuff I've done before so I'm relatively sure that it's all correct.
Finally, there are no errors being reported when I debug, although the updates don't seem to be coming through from the text fields (txtContent.Text isn't changing to reflect the new value).
Thanks in advance for your assistance.
EDIT 1:
Thanks for the updates, I've looked at the CInt(Request.Querystring("i")) issue, and it doesn't solve the problem. I set a breakpoint on the start of the procedure and hovered over txtContent.Text to check the value of it's Text property in the intellisense. Bear in mind that the debugger is still on the first line of the procedure. The txtContent.Text property as shown by the debugger at this point contains none of the changes I made while testing.
Sometimes I find refactoring code to remove duplication can be really helpful in debugging (and long-term maintenance). Here is your code with the duplicated logic centralized a bit. My guess is that your problem is that you are passing a String instead of an Integer to your SP for ArticleID. You'll see that I did a CInt(Request.QueryString("i")) to ensure that you got an integer to work with instead of the string that Request.QueryString("i") returns.
You may also want to look at my TODO comment and put a breakpoint there and use the debugger to make sure that everything you expect made its way into the cmd.Parameters collection.
As far as the stored procedure goes, I don't see anything wrong with it.
Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnSubmit.Click
Dim categoryText As String = lstCategory.SelectedItem.Text
Dim articleTitle = txtNewTitle.Text
Dim articleContent = txtContent.Text
Dim conn As New SqlConnection("Data Source=.\SQLEXPRESS; Initial Catalog=LogansArchive; Integrated Security=True;")
Dim cmd as SqlCommand
If (Request.QueryString("a") = "edit") Then
Dim articleId = CInt(Request.QueryString("i")) ' Convert provided Article ID to Int
cmd = New SqlCommand("UpdateArticle", conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("#ArticleID", articleId)
Else
cmd = New SqlCommand("AddArticle", conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("#Date", Now)
End If
cmd.Parameters.AddWithValue("#Category", categoryText)
cmd.Parameters.AddWithValue("#ArticleTitle", articleTitle)
cmd.Parameters.AddWithValue("#ArticleContent", articleContent)
' TODO: Put a breakpoint here and inspect cmd.Parameters to be sure they all contain what you expect them to contain.
conn.Open()
cmd.ExecuteNonQuery()
conn.Close()
End Sub
Well it's taken 3 months, but I just found the answer.
The reason the debugger doesn't see any changes to my text fields is ... wait for it...
The fields are being populated from the database on page load.
I just had the same problem at work, and one of the other developers hit me with this. To fix the problem, place the code that populates the form into the following IF statement:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
' Populate the form out of the database here
End If
End Sub