So here is the predefined SQL statement that is stored in the DAO file. The values are coming from a class. The picture value is an image converted to a byte(). This class is written in VB.net. I'm in a new job and in my previous i used angular and the entity framework so writing SQL statements is new to me. I'm trying to follow existing examples from co workers but they have never inserted images into the database before so i'm kinda on my own. Yes i know i could just store the files in the server and save the paths to them in the database but for whatever reason my network team wants it stored in the database as blobs. So, here is the SQL statement.
"INSERT INTO AuthAccessID" &
"(" &
"FName," &
"MName," &
"LName," &
"Suffix," &
"Address," &
"AddressExt," &
"City," &
"State," &
"Zip," &
"LawFirm," &
"Picture," &
"AddedDate," &
"AddedBy," &
")" &
"VALUES(" &
"" & ReplaceApostrophes(pp.FName) & ", " &
"'" & ReplaceApostrophes(pp.MName) & "', " &
"'" & ReplaceApostrophes(pp.LName) & "', " &
"'" & ReplaceApostrophes(pp.Suffix) & "', " &
"'" & ReplaceApostrophes(pp.Address) & "', " &
"'" & ReplaceApostrophes(pp.AddressExt) & "', " &
"'" & ReplaceApostrophes(pp.City) & "', " &
"'" & ReplaceApostrophes(pp.State) & "', " &
"'" & ReplaceApostrophes(pp.Zip) & "', " &
"'" & ReplaceApostrophes(pp.LawFirm) & "', " &
"'" & pp.Picture & "', " &
"'" & pp.AddedDate & "', " &
"'" & ReplaceApostrophes(pp.AddedBy) & "')
the pp.Picture is the Byte(). The error i'm getting is:
Operator '&' is not defined for types 'String' and 'Byte()'
i have googled around but cannot find anything. Does anyone have any idea how to correct this? or is there a better way to write the SQL statement? If i can't get this to work the network team said i can use the server file method but they are really pushing the blob in SQL storage instead. Thanks in advance.
Always use Parameters to avoid sql injection, make you sql statement easier to write and read, and make sure you are sending the correct datatypes. Parameters will also allow apostrophes. Use the .Add method. 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
In the code below, I had to guess at the SqlDbType and Size. Check your database for the correct information.
Connections and commands are using unmanaged resources. They release these resources in their .Dispose method so this method must be called. Using...End Using blocks take care of closing and disposing objects even if there is an error.
I assumed pp was an instance of a class. I gave the class the name Person. Correct this to the real class name.
Private ConStr As String = "Your connection string"
Private Sub InsertAuthAccessID(pp As Person)
Dim sql = "INSERT INTO AuthAccessID (
FName,
MName,
LName,
Suffix,
Address,
AddressExt,
City,
State,
Zip,
LawFirm,
Picture,
AddedDate,
AddedBy)
VALUES (
#FName,
#MName,
#LName,
#Suffix,
#Address,
#AddressExt,
#City,
#State,
#Zip,
#LawFirm,
#Picture,
#AddedDate,
#AddedBy)"
Using cn As New SqlConnection(ConStr),
cmd As New SqlCommand(sql, cn)
cmd.Parameters.Add("#FName", SqlDbType.VarChar, 50).Value = pp.FName
cmd.Parameters.Add("#MName", SqlDbType.VarChar, 50).Value = pp.MName
cmd.Parameters.Add("#LName", SqlDbType.VarChar, 100).Value = pp.LName
cmd.Parameters.Add("#Suffix", SqlDbType.VarChar, 20).Value = pp.Suffix
cmd.Parameters.Add("#Address", SqlDbType.VarChar, 200).Value = pp.Address
cmd.Parameters.Add("#AddressExt", SqlDbType.VarChar, 50).Value = pp.AddressExt
cmd.Parameters.Add("#City", SqlDbType.VarChar, 100).Value = pp.City
cmd.Parameters.Add("#State", SqlDbType.VarChar, 50).Value = pp.State
cmd.Parameters.Add("#Zip", SqlDbType.VarChar, 20).Value = pp.Zip
cmd.Parameters.Add("#LawFirm", SqlDbType.VarChar, 200).Value = pp.LawFirm
cmd.Parameters.Add("#Picture", SqlDbType.VarBinary).Value = pp.Picture
cmd.Parameters.Add("#AddedDate", SqlDbType.Date).Value = pp.AddedDate
cmd.Parameters.Add("#AddedBy", SqlDbType.VarChar, 50).Value = pp.AddedBy
cn.Open()
cmd.ExecuteNonQuery()
End Using
End Sub
EDIT:
In older versions of VB that did not support multiline String literals, you can use an XML literal instead:
Dim sql = <sql>
INSERT INTO AuthAccessID (
FName,
MName,
LName,
Suffix,
Address,
AddressExt,
City,
State,
Zip,
LawFirm,
Picture,
AddedDate,
AddedBy)
VALUES (
#FName,
#MName,
#LName,
#Suffix,
#Address,
#AddressExt,
#City,
#State,
#Zip,
#LawFirm,
#Picture,
#AddedDate,
#AddedBy)
</sql>
Using cn As New SqlConnection(ConStr),
cmd As New SqlCommand(sql.Value, cn)
Too long and involved for a comment. You have the following snippet in your code:
")" &
"VALUES(" &
"" & ReplaceApostrophes(pp.FName) & ", " &
"'" & ReplaceApostrophes(pp.MName) & "', " &
That is an error. FName is a string and must be treated in exactly the same manner as you do with MName. It is missing the single quote delimiters.
More generally, this approach relies on converting all your "fields" into literals to embed them as strings within your tsql statement. So the question now becomes how do you "write" a binary literal in tsql. You would do that by generating a string like this: 0x69048AEFDD010E. Documentation for tsql constants is here. Knowing that, the next issue is how to do that in your dev language - which is not something I can answer. This look promising.
But before you go down this path, use parameterization and you NEVER have to deal with this ever again.
I come from a MSAccess background, so I code quite much the same way I did in VBA or now with VB.net
Here the code I would use:
Dim sFields() As String
sFields = Split("FName,MName,LName,Suffix,Address,AddressExt,City,State,Zip,LawFirm,AddedDate,AddedBy", ",")
Dim rst As DataTable
Dim da As SqlDataAdapter
rst = MyrstEdit("select * from AuthAccessID where id = 0", da, strcon)
With rst.Rows.Add
For Each s In sFields
.Item(s) = GetValue(pp, s)
Next
End With
da.Update(rst)
And I have two helper routines. The first one gets any class property by a "string" value.
Since by luck, you have field names and the class members are the same!
Public Function GetValue(ByRef parent As Object, ByVal fieldName As String) As Object
Dim field As FieldInfo = parent.[GetType]().GetField(fieldName, BindingFlags.[Public] Or BindingFlags.Instance)
Return field.GetValue(parent)
End Function
And then I have a datable routine - that gets me the data table, and is this:
Public Function MyrstEdit(strSQL As String, ByRef oReader As SqlDataAdapter) As DataTable
Dim mycon As New SqlConnection(strCon)
oReader = New SqlDataAdapter(strSQL, mycon)
Dim rstData As New DataTable
Dim cmdBuilder = New SqlCommandBuilder(oReader)
Try
oReader.Fill(rstData)
oReader.AcceptChangesDuringUpdate = True
Catch
End Try
Return rstData
End Function
So, to get all the data types and structure? I pass a dummy sql that returns no rows. (no rows are returned, but we DO GET the valuable table data types when we do this dummy table pull!). In most cases, if the PK is a autonumber, then I use id = 0.
that same MyRstEdit() code bit has tons of uses! You can now deal with a table in a nice structure, loop it, shove it into a combo box, or datagrid. And as it shows, also allows editing of the data - all with type checking.
The REAL trick and tip I am sharing here? Break out your common data routines to about 2-3 routines like MyRstEdit().
That way, you really don't have to deal with messy in-line sql, or every time you need to work on a table, you don't wire truckloads of code. And the real beauty here is that data typing is done for you - you don't have line after line of parameters, nor line after line of data typing for each column.
So, I hope this post gives you some ideas. But it also nice since I get to code much like I did in MSAccess, and that includes writing VERY little code for updates such as this.
The ideas here are just that - a different approach. The other approaches here are also just fine. (but are quite a bit more code then I perfer).
There are times when using a data table is a rather nice - and I think this is such an example.
And while I am oh so often used to referencing columns as a table collection? The cool trick here is I am also referencing each member of the class with a string too!
I'm trying to run a SQL query from VB that returns an array of all unique values (Btl) for a given day, in time ascending order.
I'm working off of someone else's code and modifying from a version that only returned a single int value each day. (e.g. '1' , but I would like it to modify it to return '1 2 3 4')
I've tried to convert the 'Btl' variable to an array like 'Btl()' but I get the error that "Value of type 'Integer()' cannot be converted to 'Integer'"
'Get 'wherecl' table
Function GetBtl(ByVal tblRS As String, ByVal nday As Integer, ByVal sqlConn As
SqlConnection) As Integer
' Get the day's sample bottle
Dim wherecl As String
wherecl = "WHERE (DATEDIFF(d, { fn NOW() }, TmStamp) = " & nday & ")"
Dim Q4 As String
' SQL cmd to get array of unique bottles each day
Q4 = "SELECT distinct BottleToSample FROM " & tblRS & " " &
wherecl &
" ORDER BY TmStamp ASC;"
'End If
Dim MCGQ4 As New SqlCommand(Q4, sqlConn)
Dim Btl As Integer = MCGQ4.ExecuteScalar 'This is the bottle number
Return Btl
End Function
This version compiles, but doesn't display any results.
I would like it to deliver "1 2 3 4" for example
As an unknown (presumably) number of bottles will be read, it is a good idea to put the data into a List(Of Integer) - a List will expand its capacity automatically when items are added to it. If you actually need the data in an array, that can easily be done.
When using a connection to a database, that connection should be opened, the data read, and then the connection disposed of immediately. SQL Server Connection Pooling makes that efficient. In the case of the code in the question, I achieved that by using the connection string from the connection. The Using Statement takes care of freeing up unmanaged resources even if something goes wrong.
Function GetBtl(ByVal tblRS As String, ByVal nday As Integer, ByVal sqlConn As SqlConnection) As Integer()
Dim sampleBottles As New List(Of Integer)
' Get the day's sample bottles
Dim wherecl As String = "WHERE (DATEDIFF(d, { fn NOW() }, TmStamp) = " & nday & ")"
' SQL cmd to get array of unique bottles each day
Dim Q4 As String = "SELECT distinct BottleToSample FROM " & tblRS & " " & wherecl & " ORDER BY TmStamp ASC;"
Using conn As New SqlConnection(sqlConn.ConnectionString),
MCGQ4 As New SqlCommand(Q4, conn)
conn.Open()
Dim rdr = MCGQ4.ExecuteReader()
While rdr.Read()
sampleBottles.Add(rdr.GetInt32(0))
End While
End Using
Return sampleBottles.ToArray()
End Function
Also, I notice that there is a line Dim Btl As Integer = MCGQ4.ExecuteScalar in the code in the question, which indicates that Option Strict is set to "Off". That is a sub-optimal condition; it would be a good idea to use Option Strict On. It could take some effort to correct all the code with that, but it will end up removing variable type problems.
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.
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