Why does using Update query give me different result instead of updating data in vb.net - vb.net

I'm trying to update data in textbox and this error show up. Is it incorrect query?
Try
Dim Str = "UPDATE userinfo SET firstname='" & TextBox1.Text.ToUpper & "',lastname='" & TextBox3.Text.ToUpper & "'," &
"WHERE id='" & Label15.Text
connection.Open()
Dim mysc2 As New MySqlCommand(Str, connection)
mysc2.ExecuteNonQuery()
MsgBox("User successfully updated!", MsgBoxStyle.Information)
connection.Close()
Me.Close()
Catch ex As Exception
MsgBox(ex.Message)
connection.Close()
End Try

As noted, it not only less then ideal to try and concat all those things together, but as you can see it is ALSO really easy to mess up the sql string.
So, your sql looks wrong. It should be this:
Dim Str as string
Str = "UPDATE userinfo SET firstname = '" & TextBox1.Text.ToUpper & "'," & _
"lastname = '" & TextBox3.Text.ToUpper & "'" & _
" WHERE id= " & Label15.Text
Now of course, we don't know if "ID" is a string, or a number type. And if it is a number, then you do NOT surround the Lable15.Text value with single quotes. And you had a extra "," after the list textbox 3 in your sql.
But, as noted, the suggestion here is to use parameters. While this is a bit wee extra code, such code is LESS prone to concatenation errors, is easier to read, and in fact easier to write. (you can hit ctrl-d in the code editor to duplicate the line you are on!!!).
and of course, using parameters ALSO gives you AUTOMATIC data typing. So you don't have to worry or think about those extra parameters. And not only do numbers have no single quotes around them, date delimiters can even be more of a challenge.
Along without the messy concatenation?
Along without the data type issue (quotes or not????)
We ALSO get protection from SQL injection.
So, we getting a real nice truckload of bonus features. I have ALWAYS lamented that many suggest to use parameters, but then don't point out the benefits (beyond that of sql injection).
So you might wind up with a extra line or two of code, but that code is LESS error prone, and in most cases will be easier to read, but ALSO MUCH more able to allow you to add more parameters - all without messy string concatenations.
So, our code can be this:
Dim Str As String
Str = "UPDATE userinfo SET firstname = #firstName, lastname = #lastname WHERE id = #id"
Using cmdSQL As New MySqlCommand(Str, connection)
cmdSQL.Parameters.Add("#firstname", SqlDbType.NVarChar).Value = TextBox1.Text
cmdSQL.Parameters.Add("#lastname", SqlDbType.NVarChar).Value = TextBox3.Text
cmdSQL.Parameters.Add("#id", SqlDbType.Int).Value = Label15.Text
cmdSQL.Connection.Open()
cmdSQL.ExecuteNonQuery()
End Using
Some interesting things to note:
First: it really nice to read, right?
Second: Easy to code.
When I typed in this:
cmdSQL.Parameters.Add("#firstname", SqlDbType.NVarChar).Value = TextBox1.Text
While on above, I hit ctrl-d two times. that duplicated the above line.
So now, I just "cursor" up/down into the parameter value area almost like editing a column in Excel. (I did not HAVE to type those extra lines - but just edit the values and change the textbox/label values.
And BETTER is while typing in that code, I can EASY see, and read, view the nice sql line right above in code - so I just read off the parameters as I type, and edit the two additonal parameters rows. So, in fact, I actually wind up typing LESS then your posted code. And with the nice sql text in view - I have a LOWER brain work load - I don't have to remember the parameters - I just read them!
Next up:
SQL server does not care about upper vs lower case - so you don't need to worry about upper and lower - they will match regardless.
More next up:
By wrapping the whole deal inside of a using, then we don't have to close the connection - this is correctly handled by the using block (it cleans up for you).
More more next up:
And because we don't use string concatenation for the values, and DO NOT have to worry about quotes or not, and also get sql injection protection?
We ALSO get STRONG data typing.
In other words, the values from the controls are CAST to the correct data types required by sql server. I mean, often you can munge in a number with quotes as a string - sql server might work, might not! - but this way, automatic taking care of the quotes (or no quotes for numbers) is done for you!
so the updated original sql string should work, but give the parameters a try, and while the using block is a extra line of code, we don't have to close the connection, so we get back that 1 line of code!
The major goal here is it not that you must do this, or do that! Often when starting out, such advice is not all that helpful. Saying don't do that? Geesh - what kind of help is that.
In above, I not only said to use parameters, but I ALSO made a long and impassioned case as to why they are better.
The strong data typing is really nice, and protection from SQL injection is in fact a bonus feature here!
The real gold here is that we wound up with not a lot of code, and code that is easier to maintain, easier to read, and even MORE so if we decide in the future to add some more text boxes etc. on the form.
I mean, lets say you have 4 or 5 text boxes. Can you imagine then how absolute difficult that long huge concatenated string will be to edit, look at, and more so try to debug errors!
So must you use parameters? no, you don't have to, but once you try the above?
You adopt the above because the code is less mental effort and work on your part - and that's really what great code is all about.
I not that great of a coder, and thus your long string sql is too hard for me, and too great of a effort. Only those really good coders can figure out that mess. For me, you have to think as me as a one cell creature - limited brain power. The same goes for great pool players. They actually don't make the hard and difficult shots - they set themselves up in such a way that they never have to make those hard shots, or in this case read and maintain difficult and complex code.
Your code can work - but it is beyond my pay grade and ability to read and maintain such code!
Good luck!

Related

.HasRows() function on data reader taking forever

I have some simple code, an if statement checking the HasRows of a data reader and for some reason when I run my code in Visual Studio 2017 it takes forever to evaluate and return the answer (while writing this, my code has been running for 4 minutes). Any suggestions?
Dim cmd As OdbcCommand
Dim rdr As OdbcDataReader
cmd = New OdbcCommand("select GLPN,GLFY,GLDCT,GLDOC,GLCO,GLDGJ,GLANI,GLSBL,GLLT,GLCRCD,GLAA,GLU,GLGLC,GLEXA,GLICUT,GLR2,GLR1,GLSFX,GLOKCO" _
& ",GLEXR,GLODOC,GLPKCO,GLPDCT,GLCN,GLDKJ,GLVINV,GLIVD,GLPO,GLDCTO,GLLNID,GLTORG,GLAN8,GLICU,GLOPSQ,GLJBCD" _
& ",GLACR,GLABR2,GLABR1,GLDGJ,GLLT,GLCRCD,GLEXA,GLICUT,GLEXR,GLDKJ,GLIVD,GLAN8,GLICU,GLACR,GLKCO,GLSBLT,GLOBJ,GLSUB,GLJELN,GLEXTL,GLCRR,GLBCRC" _
& " from " _
& "PRODDTA.F0911 where GLPOST = 'P' and GLDGJ >= ? and GLDGJ <= ? and (GLLT = 'AA' or GLLT = 'CA') and GLDOC = 206940", cnn)
cmd.Parameters.Add("?GLUPMJs", OdbcType.Int).Value = todaysdate - 14
cmd.Parameters.Add("?GLUPMJe", OdbcType.Int).Value = todaysdate
cnn.Open()
cmd.CommandTimeout = 300
rdr = cmd.ExecuteReader
If rdr.HasRows() Then
'Do a bunch of stuff
End if
Edit1: Still getting the funny issue but it I have noticed it's only in one spot, I have the "HasRows()" Check in multiple spots and it is working fast 3ms and such. it's only on the one query.
Edit2: The query I referenced above runs on SQL developer very fast total of 1.202 seconds last time I tried, it also returns no messages.
Edit3: I am wondering if it has something to do with the amount of fields I am returning, the other queries that run fast on this line are returning much smaller field counts.
This may not speed it up any but have you tried??
While rdr.Read()
'Do a bunch of stuff
End While
EDIT:
Dim dt As DataTable = New DataTable
dt.Load(rdr)
If dt.Rows.Count >= 0 Then
'Do a bunch of stuff
End If
Maybe the problem is that the query is taking a lot to be executed. If you are only interested in checking the existence of records you could perform a query with "SELECT TOP 1 1" and use ExecuteScalar instead of ExecuteReader.
It seems that you are not the only one having trouble with the HasRows method. In this post, the OP stated a problem with that method, although it is SQL-SERVER. But I think the solution might be useful, since both DataReaders inherit from the same class and they are closely related.
If you read the post that they reference in the question and the most voted answer, you'll see that they concluded that HasRows showed a buggy behavior when the SQL query execution returned not only some data results, but also a message. They ended up using an alternative way to check if there were any data on the data reader based on the Read() method, which proved to be more reliable.
In their case, the problem was that HasRows property was set to false when the reader actually contained data, but maybe it is related to your performance problem. You should definitely give a try to the Read method just to be sure that your query is not the problem.
P.S: another interesting link with a person reporting the same problem.
The reason for the delay on HasRows() is because the ExecuteReader didn't actually execute the query, it Defers the execution until it's needed. HasRows is the one executing it. thanks to Dwillis for pointing it out.
I tested this by putting a line of code ahead of it and this is where the delay sat and when hasRows ran it was fast.
Interestingly the same sql run in Sql Management Studio runes in 1.5 seconds returning all rows, yet in the Code it takes for ever which is a different issue but also may be due to ODBC settings.

MS Access - SQL append query behavior is erratic

I've been working on an Access database for the last couple weeks, and it's my first project with the tool. Dealing with append queries seems to have become an utter nightmare, and is incredibly frustrating. Even more so because it seems to have simply stopped working in any consistent manner overnight.
The SQL query that I have written goes thus:
PARAMETERS noteDetails LongText, noteTime DateTime, srcUserID Long;
INSERT INTO tblNotes (NOTE_DETAILS, NOTE_TIME_CREATED, NOTE_SOURCE_USER)
VALUES (noteDetails, noteTime, srcUserID)
In tblNotes:
NOTE_ID is an AutoNumber
NOTE_DETAILS is a Long Text
NOTE_TIME_CREATED is a Date/Time
NOTE_SOURCE_USER is a Number
The way that I'm running this query is through VBA:
Set qdf = CurrentDb.QueryDefs("qerApndNote")
qdf.Parameters(0).Value = txtDetails.Value
qdf.Parameters(1).Value = Now()
qdf.Parameters(2).Value = getCurrentUserID()
qdf.Execute dbFailOnError
qdf.Close
Set qdf = Nothing
' Where CurrUserID is a global long
' txtDetails.Value is a textbox's contents
' Now() is the VBA built-in function to return a date/time combo
I have attempted to run this query manually from the navigation bar, and it works fine when done in that manner.
However, running it from VBA has resulted in such things as there being no time / date inserted, sometimes a user ID is not inserted, sometimes both, sometimes even the details text is missing.
What is it that I'm missing? Is there any general advice for users of MS Access to follow that I am not? I'm aware that NOTE is a restricted word in Access, but I really don't think that should apply here, right?
Thanks in advance!
EDIT: The form that I'm passing data from is called frmNewNote, and there is a control in it named txtDetails. It's just a regular textbox. Don't really know what else to share about that.
The getCurrentUserID function is in a module, modGlobal:
Public CurrUserID As Long
Public Function getCurrentUserID() As Long
getCurrentUserID = CurrUserID
End Function
Public Function setCurrentUserID(CurrID As Long)
CurrUserID = CurrID
End Function
It's about as barebones as you can get, really. And there is never a circumstance that you'll get to the form before SetCurrentUserID has been called during your... session? There's a login form involved.
#Andre's code for logging:
0 noteDetailsText This is a note test
1 noteTimeCreated 9/6/2017 10:28:45 AM
2 srcUserID 1
As for my architecture, um, it's just the single database file right now, on the desktop. The entire function/sub is run when you click a button, btnEnter. It does some other stuff before it gets to the SQL statement bit - checks for null values and prompts user for entries if that's the case.
I just remembered something:
MS Access 2013 calling insert queries from VBA with strange errors
You have a LongText parameter. These don't really work. See also https://stackoverflow.com/a/37052403/3820271
If the entered notes will always be <= 255 characters, change the parameter to ShortText.
If the text can be longer, you'll have to use either SunKnight0's approach with a concatenated INSERT statement.
Or use a Recordset and its .AddNew method, which will be a similar amount of code to your current solution, but also be completely safe from injection or formatting issues.
You are doing way more work than you have to. All you need is:
DoCmd.RunSQL("INSERT INTO tblNotes (NOTE_DETAILS, NOTE_TIME_CREATED, NOTE_SOURCE_USER) VALUES ('" & Me.txtDetails & "',Now()," & CurrUserID & ")")
Note the change from txtDetails.Value to Me.txtDetails which is what may have been messing you up. This of course assumes the code runs in the form's context, otherwise you have to get he value of the text field using a reference to the form.
The only other thing to consider is making sure Me.txtDetails does not have any single quotes, so probably use Replace(Me.txtDetails,"'","''") instead.
That way you can also replace DoCmd.RunSQL with MsgBox to troubleshoot the exact query.

Determine Number of Lines in a String Read in from an Access Database

I am writing a program in Visual Basic that writes and reads to and from a Microsoft Access Database. When reading from the database, one of the functions that I am trying to perform is to determine the number of lines in a multi-line string that was written to the database and then subsequently read from the database. Here's what I have tried so far with no luck.
Dim stringLines() As String = databaseReader("multilineString").ToString.Split(CChar("Environment.NewLine"))
Dim stringLinesCount As Integer = stringLines.Length
For some reason, this always results in stringLinesCount being equal to one, regardless of how many lines the string has. In this example, I am using Environment.NewLine, but I have tried \n, \r, vbCr, vbLf, and vbCrLf as well, and they all result in a value of one. Since none of these seem to be working, what can I use instead to determine the number of lines?
Edit:
Dim splitCharacters() As Char = {CChar(vbCrLf), CChar(vbCr), CChar(vbLf), CChar(Environment.NewLine), CChar("\n"), CChar("\r")}
Dim stringLines() As String = databaseReader("multilineString").ToString.Split(splitCharacters)
Dim stringLinesCount As Integer = stringLines.Length
Since Chris Dunaway provided the answer that I view as helpful but posted it as a comment, here's what he said:
VB cannot use C# style escape sequences, so CChar("\n") and CChar("\r") is meaningless in VB. Also, calling CChar("Environment.NewLine") is wrong because you are trying to convert the actual string "Environment.NewLine" to a single character, which obviously won't work. You can just use Environment.Newline directly in the call to String.Split.
If Chris decides to post his comment as an answer, please let me know so that I may remove this.

SqlConnection.Open() Establishing a connection EVERY query?

In my Winforms app which uses a remote database, I have the function below. (I also have two other functions which work similarly: One for scalar queries which returns zero upon failure, the other for updates and inserts which returns false upon failure.)
Currently, ALL data manipulation is pumped through these three functions.
It works fine, but overall please advise if I'd be better off establishing the connection upon launching my app, then closing it as the app is killed? Or at another time? (Again, it's a windows forms app, so it has the potential to be sitting stagnant while a user takes a long lunch break.)
So far, I'm not seeing any ill effects as everything seems to happen "in the blink of an eye"... but am I getting data slower, or are there any other potential hazards, such as memory leaks? Please notice I am closing the connection no matter how the function terminates.
Public Function GetData(ByVal Query As String) As DataTable
Dim Con As New SqlConnection(GlobalConnectionString)
Dim da As New SqlDataAdapter
Dim dt As New DataTable
Try
Con.Open()
da = New SqlDataAdapter(Query, Con)
Con.Close()
da.Fill(dt)
Catch ex As Exception
Debug.Print(Query)
MsgBox("UNABLE TO RETRIEVE DATA" & vbCrLf & vbCrLf & ex.Message, MsgBoxStyle.Critical, "Unable to retrieve data.")
End Try
da.Dispose()
Con.Close()
Return dt
End Function
There are exceptions to this, but best practices in .Net do indeed call for creating a brand new connection object for most queries. Really.
To understand why is, first understand actually connecting to a database involves lots of work in terms of protocol negotiations, authentication, and more. It's not cheap. To help with this, ADO.Net provides a built-in connection pooling feature. Most platforms take advantage of this to help keep connections efficient. The actual SqlConnection, MySqlConnection, or similar object used in your code is comparatively light weight. When you try to re-use that object, you're optimizing for the small thing (the wrapper) at the expense of the much larger thing (the actual underlying connection resources).
Aside from the benefits created from connection pooling, using a new connection object makes it easier for your app to scale to multiple threads. Imagine writing an app which tries to rely on a single global connection object. Later you build a process which wants to spawn separate threads to work on a long-running task in the background, only to find your connection is blocked, or is itself blocking other normal access to the database. Worse, imagine trying to do this for a web app, and getting it wrong such that the single connection is shared for your entire Application Domain (all users to the site). This is a real thing I've seen happen.
So this is something that your existing code does right.
However, there are two serious problems with the existing method.
The first is that the author seems not to understand when to open and when to close a connection. Using the .Fill() method complicates this, because this method will open and close your connection all on its own.1 When using this method, there is no good reason to see a single call to .Open(), .Close(), or .Dispose() anywhere in that method. When not using the .Fill() method, connections should always be closed as part of a Finally block: and the easiest way to do that is with Using blocks.
The second is SQL Injection. The method as written allows no way to include parameter data in the query. It only allows a completed SQL command string. This practically forces you to write code that will be horribly vulnerable to SQL Injection attacks. If you don't already know what SQL Injection attacks are, stop whatever else you're doing and go spend some time Googling that phrase.
Let me suggest an alternative method for you to address these problems:
Public Function GetData(ByVal Query As String, ParamArray parameters() As SqlParameter) As DataTable
Dim result As New DataTable()
Using Con As New SqlConnection(GlobalConnectionString), _
Cmd As New SqlCommand(Query, Con),
da As New SqlDataAdpapter(Cmd)
If parameters IsNot Nothing Then Cmd.Parameters.AddRange(parameters)
Try
da.Fill(result)
Catch ex As Exception
Debug.Print(Query)
'Better to allow higher-level method to handle presenting the error to the user
'Just log it here and Rethrow so presentation tier can catch
Throw
End Try
End Using 'guarantees connection is not left hanging open
Return result
End Function
1See the first paragraph of the "Remarks" section.
This isn't a real "answer" to my own question, but I have something to add and I wanted to add some code.
To Joe: Thank you, my code is well on the way to using parameterized queries almost exclusively. Although I knew what SQL injection attacks were, and that they're a pretty big deal, here's my exuse: In the past I had used stored procedures for parameterized queries, and I kinda hate writing those and for the first year my code will be used only within by my small company of 5 employees who are family members... I had planned to switch everything to stored procedures later if I sold the software. This approach is better and I will probably not need stored procedures at all.
I especially like how elegantly parameterized queries handle dates, as I don't have to convert dates to appropriate text. Much easier.
Anopther advantage I'm seeing: Sometimes a "Save button" must execute either Insert or Update, depending on whether the record displayed is new. Using parameters allows me to write two alternate short basic queries, but to use the same parameters for either with less code.
Overall, this means a whole lot less code-intensive construction of the query string.
The part I didn't have, and I learned to do it elsewhere, was assigning the parameter array, calling the procedure, so I'm including an example here hoping others find it useful:
Dim query As String = "Select Phone from Employees where EmpNo = #EmployeeNumber and Age = #Age"
Dim params As SqlParameter() = {
New SqlParameter("#EmployeeNumber", txtEmployeeNumber.Value),
New SqlParameter("#Age", txtAge.Value)
}
Dim Phone as String = GetData(query, params).Rows.Item(0)

VB.NET - I'm curious, why does the return from "Right" not work as a string?

If I have a string like input = "AA["
If Right(input, 1) = "[" Then Do stuff
The If statement returns false, even if I try converting things around to chars, etc etc. But if I do this is returns true:
Dim temp As String = Right(input, 1)
If temp = "[" Then Do Stuff
I like knowing little semantics like this, any idea why it comes out this way?
Or don't use Right at all since this is .Net
Dim s As String = "AAAAAAA]"
If s.Substring(s.Length - 1, 1) = "]" Then
Stop
End If
'or
If s(s.Length - 1) = "]" Then
Stop
End If
I've seen weird behavior like that when debugging.
In fact, today I had something simlar
Dim records As Integer
records = If(o.dr Is Nothing, o.ADO.rs.RecordCount, o.ADO.DS.Tables("tbl").Rows.Count)
That should work, using the rs.RecordCount when dr is nothing, otherwise using the Rows.Count. It didn't, records was ending up as zero. Rewrote it as a full if then/else block and it works.
It's never my first thought that the compiler/debugger/ide is messing things up, but you should keep it in the back of your mind for consideration: the programmers that wrote those programs are just as human and fallible as you or me.
It shouldn't. Are you sure you didn't have a typo? The result of RIGHT is a string, and if the input was truly "AA[" the IF will have passed.
I've never had VB act wonky on something like this.
If the code appears in a form, then the .Right property of the form overrides the string manipulation function. You need to specify the parent namespace - e.g. VisualBasic.Right - to ensure that you get the correct method.
I think you may have some kind of strange overload confusion occuring here.
You are specifiing "Right" (which could be calling a local "Right" function).
The function your implying is "Microsoft.VisualBasic.Strings.Right" in most of my code ends up being "Strings.Right" due to the global import.
I would try changing you code to the below and see if it still happens, in order to rule out some overload/scope confusion. (and/or the reduced "Strings.Right")
If Micosoft.VisualBasic.Strings.Right(input, 1) = "[" Then Do stuff