This is my function:
Function SQL_InsertUpdate(mySQLConnection As OleDbConnection, mySQLCommand As String, mySQLTable As String, mySQLTableColumns() As String, myParameters() As String)
Dim SQLCommand As OleDbCommand = New OleDbCommand(mySQLCommand, mySQLConnection)
Dim myStringConstruct = mySQLCommand & " " & mySQLTable & " ("
'==============
For Each item In mySQLTableColumns
myStringConstruct = myStringConstruct & item & ", "
Next
myStringConstruct = Strings.Left(myStringConstruct, Len(myStringConstruct) - 2)
myStringConstruct = myStringConstruct & ") VALUES ("
For i As Integer = 0 To mySQLTableColumns.Length - 1
myStringConstruct = myStringConstruct & "#" & mySQLTableColumns(i) & ", "
SQLCommand.Parameters.AddWithValue("#" & mySQLTableColumns(i), myParameters(i))
Next
myStringConstruct = Strings.Left(myStringConstruct, Len(myStringConstruct) - 2)
myStringConstruct = myStringConstruct & ")"
SQLCommand.ExecuteNonQuery()
End Function
This is how I call the function:
Dim myParameters() As String = ({myNewID.ToString, myUser.ToString, myDepartment.ToString, mySubsidiary.ToString, myTitle.ToString, myRecurrence.ToString, myImpact.ToString, myTimeSaved.ToString, myPriority.ToString, myStatus.ToString, myTechnology.ToString, myDeveloper.ToString, myCostSave.ToString, myDescription.ToString, myCommentary.ToString, myDateSubmitted.ToString, myDateModified.ToString, myInReviewDate.ToString, myManagerReviewDate.ToString, myDigitalReviewDate.ToString, myRejectedDate.ToString, myInProgressDate.ToString, myDevelopedDate.ToString, myImplementedDate.ToString})
Dim mySQLTableColumns() As String = ({"ID", "myUser", "myDepartment", "mySubsidiary", "myTitle", "myRecurrence", "myImpact", "myTimeSaved", "myPriority", "myStatus", "myTechnology", "myDeveloper", "myCostSave", "myDescription", "myCommentary", "myDateSubmitted", "myDateModified", "myInReviewDate", "myManagerReviewDate", "myDigitalReviewDate", "myRejectedDate", "myInProgressDate", "myDevelopedDate", "myImplementedDate"})
SQL_InsertUpdate(SQLConnection, "INSERT INTO", "SIMSBase", mySQLTableColumns, myParameters)
This is the constructed command string output:
INSERT INTO SIMSBase (ID, myUser, myDepartment, mySubsidiary, myTitle,
myRecurrence, myImpact, myTimeSaved, myPriority, myStatus,
myTechnology, myDeveloper, myCostSave, myDescription, myCommentary,
myDateSubmitted, myDateModified, myInReviewDate, myManagerReviewDate,
myDigitalReviewDate, myRejectedDate, myInProgressDate,
myDevelopedDate, myImplementedDate) VALUES (#ID, #myUser,
#myDepartment, #mySubsidiary, #myTitle, #myRecurrence, #myImpact,
#myTimeSaved, #myPriority, #myStatus, #myTechnology, #myDeveloper,
#myCostSave, #myDescription, #myCommentary, #myDateSubmitted,
#myDateModified, #myInReviewDate, #myManagerReviewDate,
#myDigitalReviewDate, #myRejectedDate, #myInProgressDate,
#myDevelopedDate, #myImplementedDate)
This is the error I receive:
$exception {"Syntax error in INSERT INTO statement."} System.Data.OleDb.OleDbException
Now I have no clue what syntax error I could have, I looked here for another person who has no issue in this Stack question: Insert data into SQL database in VB.NET, my syntax is similar.
I don't know what is wrong, could it give out a syntax error if the Access database columns are not Data Type Short/Long Text?
The parameters are added properly (checked the debug).
Actually, I often find it too much work to create a complex insert routine. And even worse is I often don't care or want to supply all of the columns.
.net as a result has what is called a command builder for you.
And this quite much means you can write a lot of your code in a simular way to how VBA code in Access works.
So, say I want to add a new row - the table might have 50 columns, but I don't really care.
So, I can write the code this way:
Dim rstHotels As DataTable
rstHotels = MyRst("SELECT * FROM tblHotels WHERE ID = 0")
' now add 3 new hotels
For i = 1 To 3
Dim OneRow = rstHotels.NewRow
OneRow("HotelName") = "Hotel #" & i
OneRow("City") = "City #" & i
OneRow("FirstName") = "Test First name #" & i
OneRow("Active") = True
rstHotels.Rows.Add(OneRow)
Next
' ok, added rows to rstHotels - now write back to database
MyRstUpDate(rstHotels, "tblHotels")
' or update 5 rows and do compplex processing to exising data.
Dim rstFun As DataTable = MyRst("SELECT * from tblHotels where City = 'Banff'")
For Each MyRow As DataRow In rstFun.Rows
MyRow("Active") = True
' more complex cpde here
Next
' now send data changes back to database
MyRstUpdate(rstFun, "tblHotels")
So, note how we don't have to have some complex insert statement, and we don't hve to write some loop that gives one hoot about the number of columns. So the .net data operations have build in all this stuff for you - little or even next to no reason for you to try and re-invent the wheel here.
And the two handy dandy code routines I have? The are :
Public Function MyRst(strSQL As String) As DataTable
Dim rstData As New DataTable
Using conn As New OleDbConnection(My.Settings.AccessDB)
Using cmdSQL As New OleDbCommand(strSQL, conn)
conn.Open()
rstData.Load(cmdSQL.ExecuteReader)
rstData.TableName = strSQL
End Using
End Using
Return rstData
End Function
Public Sub MyRstUpdate(rstData As DataTable, strTableName As String)
Using conn As New OleDbConnection(My.Settings.AccessDB)
Using cmdSQL As New OleDbCommand("SELECT * from " & strTableName, conn)
Dim da As New OleDbDataAdapter(cmdSQL)
Dim daUP As New OleDbCommandBuilder(da)
conn.Open()
da.Update(rstData)
End Using
End Using
End Sub
Now, I am really rather free to just code out my general routines.
So, you need to say load up a grid, or even a combo box? You can now do this:
ListBox1.DataSource = MyRst("SELECT ID, Salutation from tblGender ORDER BY Salutation")
So, for a simple insert, or even edit of some rows? No need to create some monster huge insert statement with a boatload of parameters. Just create a data table, and then use a simple data row to either add new rows, or even update existing ones.
The beauty of above is not only do you eliminate a boatload of parameters, but you also get parameter safe, and even type conversions. So, you can for example do this:
OneRow("InvoiceDate") = Date.Today
Thus a strong typed value of "money" or integer, or datetime can be used in code - and no messey format convertions are required in most cases.
This so called "data base" first can be really handy, and often for some operations this is a lot less setup time and learning curve then say using EF, or even the previous dataset designer (EF = "Entity framework", which works really much like the older data set designer system - but introduction of these object model systems can be a big system to chew on when you just starting out).
But, no, don't write your own looping code to write out and create all the columns for a update command. (or insert command - note how that ONE routine can handle both updates or inserts. And you can even use row.Delete and then call tht update routine - it will also work!!.
If you think about this, that really amounts to a lot of work, and built in systems exist for this propose - saves you having to re-invent the wheel.
I'm dumb.
The order of the operations was off, this is what the function should have looked like:
Function SQL_InsertUpdate(mySQLConnection As OleDbConnection, mySQLCommand As String, mySQLTable As String, mySQLTableColumns() As String, myParameters() As String)
Dim myStringConstruct = mySQLCommand & " " & mySQLTable & " ("
'==============
For Each item In mySQLTableColumns
myStringConstruct = myStringConstruct & item & ", "
Next
myStringConstruct = Strings.Left(myStringConstruct, Len(myStringConstruct) - 2)
myStringConstruct = myStringConstruct & ") VALUES ("
For i As Integer = 0 To mySQLTableColumns.Length - 1
myStringConstruct = myStringConstruct & "#" & mySQLTableColumns(i) & ", "
Next
myStringConstruct = Strings.Left(myStringConstruct, Len(myStringConstruct) - 2)
myStringConstruct = myStringConstruct & ")"
Dim SQLCommand As OleDbCommand = New OleDbCommand(myStringConstruct, mySQLConnection)
For i As Integer = 0 To mySQLTableColumns.Length - 1
SQLCommand.Parameters.AddWithValue("#" & mySQLTableColumns(i), myParameters(i))
Next
SQLCommand.ExecuteNonQuery()
End Function
Basically, I was passing an incomplete command.
The database is locked error appears even after I have disposed all the commands. I'm trying to write to the database and it is failing on the INSERT command at the bottom. I had it working before but for some reason it has started to fail now.
Sub Btn_SubmitClick(sender As Object, e As EventArgs)
If MsgBox("Are you sure?",vbYesNo,"Submit?") = 7 Then
'returns to previous screen
Else
'commences insert into database
Rows = (dataGridView1.RowCount - 1)
While count < rows
'putting grid stuff into variables
DateStart = dataGridView1.Rows(Count).Cells(0).Value.ToString
DateEnd = dataGridView1.Rows(Count).Cells(2).Value.ToString 'note other way round
TimeStart = dataGridView1.Rows(Count).Cells(1).Value.ToString
TimeEnd = dataGridView1.Rows(Count).Cells(3).Value.ToString
TotalHours = dataGridView1.Rows(Count).Cells(4).Value.ToString
OccuranceNo = dataGridView1.Rows(Count).Cells(5).Value.ToString
'fetching reason ID for Storage
SQLcommand = SQLconnect.CreateCommand
SQLcommand.CommandText = "SELECT Reason_ID FROM Reasons WHERE Reason_Name = '" & dataGridView1.Rows(Count).Cells(6).Value.ToString & "'"
SQLreader = SQLcommand.ExecuteReader
ReasonID = SQLreader("Reason_ID")
SQLcommand.Dispose
'fetching site ID for storage
SQLcommand = SQLconnect.CreateCommand
SQLcommand.CommandText = "SELECT Site_ID FROM Sites WHERE Site_Name = '" & dataGridView1.Rows(Count).Cells(7).Value.ToString & "'"
SQLreader = SQLcommand.ExecuteReader
SiteID = SQLreader("Site_ID")
SQLcommand.Dispose
Oncall = dataGridView1.Rows(Count).Cells(8).Value.ToString
'increment counter
Count = Count + 1
'send to database
SQLcommand = SQLconnect.CreateCommand
SQLcommand.CommandText = "INSERT INTO Shifts (Staff_ID, Date_Shift_Start, Date_Shift_End, Time_Shift_Start, Time_Shift_End, Total_Hours, Occurance_No, Site_ID, On_Call_Req, Rate, Approved, Reason_ID) VALUES ('" & userID & "' , '" & DateStart &"' , '" & DateEnd & "' , '" & TimeStart & "' , '" & TimeEnd & "' , '" & TotalHours & "' , '" & OccuranceNo & "' , '" & SiteID & "' , '" & Oncall & "' , '"& "1" & "' , '" & "N" & "' , '" & ReasonID & "')"
SQLcommand.ExecuteNonQuery()
SQLcommand.Dispose
End While
MsgBox("Ok")
End If
End Sub
There are several things which ought be changed in the code shown. Since none of the Connection, Command or Reader objects are declared in the code, they must be global objects you are reusing. Don't do that.
There can be reasons for one persistent connection, but queries are very specific in nature, so trying to reuse DbCommand and DataReaders can be counterproductive. Since these work closely with the DbConnection, all sorts of bad things can happen. And it means the root of the problem could be anywhere in your code.
The following will loop thru a DGV to insert however many rows there are.
Dim SQL = "INSERT INTO Sample (Fish, Bird, Color, Value, Price) VALUES (#f, #b, #c, #v, #p)"
Using dbcon As New SQLiteConnection(LiteConnStr)
Using cmd As New SQLiteCommand(SQL, dbcon)
dbcon.Open()
cmd.Parameters.Add("#f", DbType.String)
cmd.Parameters.Add("#b", DbType.String)
cmd.Parameters.Add("#c", DbType.String)
cmd.Parameters.Add("#v", DbType.Int32)
cmd.Parameters.Add("#p", DbType.Double)
Dim fishName As String
For Each dgvR As DataGridViewRow In dgv2.Rows
' skip the NewRow, it has no data
If dgvR.IsNewRow Then Continue For
' look up from another table
' just to shorten the code
userText = dgvR.Cells(0).Value.ToString()
fishName = dtFish.AsEnumerable().
FirstOrDefault(Function(f) f.Field(Of String)("Code") = userText).
Field(Of String)("Fish")
' or
'Dim drs = dtFish.Select(String.Format("Code = '{0}'", userText))
'fishName = drs(0)("Fish").ToString()
cmd.Parameters("#f").Value = fishName
cmd.Parameters("#b").Value = dgvR.Cells(1).Value
cmd.Parameters("#c").Value = dgvR.Cells(2).Value
cmd.Parameters("#v").Value = dgvR.Cells(3).Value
cmd.Parameters("#p").Value = dgvR.Cells(4).Value
cmd.ExecuteNonQuery()
Next
End Using
End Using
NOTE: Like the original code there is no Data Validation - that is, it assumes that whatever they typed is always valid. This is rarely a good assumption.
The code implements Using blocks which will declare and create the target objects (dbCommands, connections) and dispose of them when done with them. They cannot interfere with code elsewhere because they only exist in that block.
SQL Parameters are used to simplify code and specify datatypes. A side effect of concatenating SQL as you are, is that everything is passed as string! This can be very bad with SQLite which is typeless.
I would avoid firing off multiple look up queries in the loop. The original code should throw an InvalidOperationException since it never Reads from the DataReader.
Perhaps the best way to do this would be for Sites and Reasons to be ComboBox column in the DGV where the user sees whatever text, and the ValueMember would already be available for the code to store.
Another alternative shown in the answer is to pre-load a DataTable with the data and then use some extension methods to look up the value needed.
If you "must" use readers, implement them in their own Using blocks.
A For Each loop is used which provides the actual row being examined.
I'd seriously reconsider the idea of storing something like a startDateTime as individual Date and Time fields.
These are the basics if using DB Provider objects. It is not certain that refactoring that procedure shown will fix anything, because the problem could very well be anywhere in the code as a result of DBProvider objects left open and not disposed.
One other thing to check is any UI Manager you may be using for the db. Many of these accumulate changes until you click a Save or Write button. In the interim, some have the DB locked.
Finally, even though the code here is shorter and simpler, using a DataAdapter and a DataTable would allow new data in the DGV to automatically update the database:
rowsAffected = myDA.Update(myDT)
It would take perhaps 30 mins to learn how to configure it that way.
So, I have an application where I am able to drop a file into a button and the information will be shown on my database. To make things better I am trying after add something a query will run with an INNER JOIN where there are 2 tables, one of them will simply save the files info and the other one will save the date and time of that added file. I already have the LogsFile method:
Private Sub LogFileAdicionados()
Dim LogsFile As String = My.Application.Info.DirectoryPath
Dim logtext As String = lblName.Text
Dim logtext2 As String = lblSize.Text
Dim vt As String = "The file " & logtext & " com " & logtext2 & " KB " & "was added at " & TimeOfDay & " in " & Date.Today & "." & vbCrLf
My.Computer.FileSystem.WriteAllText(LogsFile + "\LogsAdded.txt", vt, True)
End Sub
So what should I do to INSERT the data and time values onto the Logs table?
As Zaggler pointed out, you need at least to attempt to do it yourself in order to post more meaningful question. However I'm going to set you on the track and see what happen...
First you need an approach on how to interact with your database. I'll show you how to do it with ADO.NET data objects, but keep in mind that there are other tools/frameworks to interact with a DB storage.
Next you need the connection string to your database. You can find hundreds of examples here.
Then you can do some fun stuff, like:
Using conn As New SqlConnection("connection string to your DB goes here")
Using cmd = conn.CreateCommand()
cmd.CommandText = "INSERT INTO Logs (fields definition) VALUES" + vt
cmd.ExecuteNonQuery()
End Using
End Using
Basically this outlining saving data to a SQL DB, using ADO.NET data objects.
can someone help me with my code, i need to check first if record exist. Well i actually passed that one, but when it comes to inserting new record. im getting the error "There is already an open DataReader associated with this Command which must be closed first." can some help me with this? thanks.
Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim reg_con As SqlConnection
Dim reg_cmd, chk_cmd As SqlCommand
Dim checker As SqlDataReader
Dim ID As Integer
Dim fname_, mname_, lname_, gender_, emailadd_, college_, password_ As String
ID = idnumber.Value
fname_ = fname.Value.ToString
mname_ = mname.Value.ToString
lname_ = lname.Value.ToString
gender_ = gender.Value.ToString
college_ = college.Value.ToString
emailadd_ = emailadd.Value.ToString
password_ = reg_password.Value.ToString
reg_con = New SqlConnection("Data Source=JOSH_FLYHEIGHT;Initial Catalog=QceandCceEvaluationSystemDatabase;Integrated Security=True")
reg_con.Open()
chk_cmd = New SqlCommand("SELECT IDnumber FROM UsersInfo WHERE IDnumber = '" & ID & "'", reg_con)
checker = chk_cmd.ExecuteReader(CommandBehavior.CloseConnection)
If checker.HasRows Then
MsgBox("Useralreadyexist")
Else
reg_cmd = New SqlCommand("INSERT INTO UsersInfo([IDnumber], [Fname], [Mname], [Lname], [Gender], [Emailadd], [College], [Password]) VALUES ('" & ID & "', '" & fname_ & "', '" & mname_ & "', '" & lname_ & "', '" & gender_ & "', '" & emailadd_ & "', '" & college_ & "', '" & password_ & "')", reg_con)
reg_cmd.ExecuteNonQuery()
End If
reg_con.Close()
End Sub
Add this string to your connection string
...MultipleActiveResultSets=True;";
Starting from Sql Server version 2005, this string allows an application to maintain multiple active statements on a single connection. Without it, until you close the SqlDataReader you cannot emit another command on the same connection used by the reader.
Apart from that, you insert statement is very dangerous because you use string concatenation. This is a well known code weakness that could result in an easy Sql Injection vulnerability
You should use a parameterized query (both for the insert and for the record check)
reg_cmd = New SqlCommand("INSERT INTO UsersInfo([IDnumber], ......) VALUES (" & _
"#id, ......)", reg_con)
reg_cmd.Parameters.AddWithValue("#id", ID)
.... add the other parameters required by the other field to insert.....
reg_cmd.ExecuteNonQuery()
In a parameterized query, you don't attach the user input to your sql command. Instead you put placeholders where the value should be placed (#id), then, before executing the query, you add, one by one, the parameters with the same name of the placeholder and its corresponding value.
You need to close your reader using checker.Close() as soon as you're done using it.
Quick and dirty solution - issue checker.Close() as a first command of both IF and ELSE block.
But (better) you don't need a full blown data reader to check for record existence. Instead you can do something like this:
chk_cmd = New SqlCommand("SELECT TOP (1) 1 FROM UsersInfo WHERE IDnumber = '" & ID & "'", reg_con)
Dim iExist as Integer = chk_cmd.ExecuteScalar()
If iExist = 1 Then
....
This approach uses ExecuteScalar method that returns a single value and doesn't tie the connection.
Side note: Instead of adding parameters like you do now - directly to the SQL String, a much better (and safer) approach is to use parametrized queries. Using this approach can save you a lot of pain in the future.
I have setup a Table Adapter in Visual Studio linked to a SQL Server db.
I've followed the MSDN tutorials and I have manually setup some queries for this TA. I think of these queries as "pre_hardcoded". I call these queries using the default code:
Me.ItemFactTableAdapter.My_Pre_Hardcoded_Query(Me.MasterDataSet.ItemFact)
I want to dynamically call data in different configuration (from the same Master Table) and thus I need a lot of these pre-hardcoded queries. So, instead of writing 1k queries I've want to use something like this:
TableName = "ItemFact"
H_Label = "ChainName"
V_Label = "ItemName"
Dim Measure As String = "Volume"
Dim Select_Clause As String = "select distinct " & H_Label & "," & V_Label & ", Sum(" & Measure & ") as " & Measure & " "
Dim From_Clause As String = "from " & TableName & " "
Dim Where_Clause As String = ""
Dim GroupBy_Clause As String = "group by " & H_Label & "," & V_Label
Dim SelectionQuery = Select_Clause & From_Clause & Where_Clause & GroupBy_Clause
Where I can dynamically update the values of "Measure" and the "H" & "V Labels".
The question is: How do I declare this SelectionQuery to be a valid part of the TA so that I can use it like:
Me.ItemFactTableAdapter.SelectionQuery (Me.MasterDataSet.ItemFact)
for dynamic query you need create generic DataAdapter:
Dim da As New SqlDataAdapter(SelectionQuery, Me.ItemFactTableAdapter.Connection)
da.Fill(Me.MasterDataSet.ItemFact)
I still haven't found an answer to my initial question ON HOW TO ADD QUERIES TO A TABLE ADAPTER, but based on #lomed answer I've done a workaround. Thus, instead of filling the TA on 1st load and then pulling data with different queries, I'm updating the whole dataset on each query. I believe this method may be more time-consuming when applied to big datasets, but for now it works.
Dim strConn As String = "Data Source=XXX;Initial Catalog=master;Integrated Security=True"
Dim conn As New SqlConnection(strConn)
Dim oCmd As New SqlCommand(SelectionQuery, conn)
Dim oData As New SqlDataAdapter(SelectionQuery, conn)
Dim ds As New DataSet
and then attach the dataset to objects like:
Chart1.DataSource = ds.Tables("Table1")
instead of:
Chart1.DataSource = Me.ItemFactBindingSource