Data Adapter Update command causing exception - sql-server

JUST A HEADS UP ----- This no longer applies for me. I fixed it by adding the following two lines to my form.load.
cb.QuotePrefix = "["
cb.QuoteSuffix = "]"
It has fixed the issue for me and I hope it sorts it out for you too.
Okay, so, I have an application in VB.net that serves as a scheduler for the trainers at the company I work at. In the edit mode (so updates can be made to the database the information is stored on) there are a number of buttons, adding and removing trainers, or adding weeks to the table.
The button causing me problems is the save/update button. It has three sets of commands within it, one for updating added columns, one for removed columns, and then a third one which simply updates other modified data. I know it probably could be more efficient but oh well, I'll get to that later.
The problem is, the last chunk of code includes "da.update(ds)" which is the data adapter updating the datasource. While this command works perfectly fine in our other app that connects to our SQL server, its causing problems here.
Any column where the first cell's value is null causes an exception saying
"Incorrect syntax near 'the column header's first two characters'."
Now, I thought this issue stemmed from - due to the exception - me using an incorrect set of names for the columns, which were the dates of the mondays of each week (so for example 01/02/2016) so it'd show Incorrect syntax near '01'. in this instance.
However, changing the naming convention did not fix this like how the exception would suggest, and it only occurs on columns where the FIRST value is a null - implying that the first trainer has nothing planned for this week right now.
Anyone have any ideas as to a way around this that doesnt involve basically filling in every null on this rather large table? I know that would work but It'd be pretty time consuming, and I am willing to do this if no other solution rears its head.
I've looked around on the internet and haven't found a solution that appeared relevant to our exact issue, so help would be very appreciated.
In case it is important - here is the function causing the issue.
Dim da As OleDbDataAdapter 'The datasets and adapters variables.
Dim da2 As OleDbDataAdapter
Public ds As DataSet = New DataSet
Public ds2 As DataSet = New DataSet
'Connection String. Connects to the server and finds the database and table listed.
cs = "Provider=SQLOLEDB;"
cs &= "Server=SOFWAREDEVSQLSE\SQLEXPRESS;"
cs &= "Database=MTS2;"
cs &= "User Id=;" 'You don't need to see that to be fair.
cs &= "Password=;" 'You don't need to see that to be fair.
sql = "Select * FROM MTS2;"
'Runs the string. Flares up a timed out error if connection could not be established.
Try
da = New OleDbDataAdapter(sql, cs)
da.Fill(ds)
da2 = New OleDbDataAdapter(sql, cs)
da2.Fill(ds2)
Catch ex As Exception
MsgBox("Connection failed. Please ensure you have a suitable connection to the Training network. Otherwise, refer to helpdesk support for further assistance.")
Me.Close()
End Try
dgvSchedule.DataSource = ds.Tables(0)
Private Function save()
'Try
''This section reads the SQL server for column names, and adds any that are listed in the DGV, but not the database. I know its a little messy but itll do.
Dim columnnum As Integer = -1
Dim columname As String
For Each column In ds.Tables(0).Columns
columnnum = columnnum + 1
columname = dgvSchedule.Columns(columnnum).HeaderText
If Not ds2.Tables(0).Columns.Contains(columname) Then
Dim SqlAddCol As String = "ALTER TABLE MTS2 ADD [" & columname.Trim() & "] nvarchar(255)"
Using con As New OleDbConnection(cs)
Using cmd As New OleDbCommand(SqlAddCol, con)
con.Open()
cmd.ExecuteNonQuery()
End Using
End Using
End If
Next
columnnum = -1
For Each column In ds2.Tables(0).Columns
columnnum = columnnum + 1
columname = ds2.Tables(0).Columns(columnnum).ColumnName
If Not ds.Tables(0).Columns.Contains(columname) Then
Dim SqlDelCol As String = "ALTER TABLE MTS2 DROP COLUMN [" & columname.Trim() & "]"
Using con As New OleDbConnection(cs)
Using cmd As New OleDbCommand(SqlDelCol, con)
con.Open()
cmd.ExecuteNonQuery()
End Using
End Using
End If
Next
ds2.Tables.Clear()
da2 = New OleDbDataAdapter(sql, cs)
da2.Fill(ds2)
da.Update(ds) ''''' The exception is thrown here. " Incorrect syntax near '01'."
DataTableColours()
MessageBox.Show("Data saved successfully. New weeks and trainers added and deleted. Changed values updated.")
'Catch
' MessageBox.Show("Data failed to update properly. Please ensure you are connected to the Baltic network and try again. If the problem persists, seek IT support.")
'End Try
End Function
The function saves the values in the data grid view (DGVSchedule) to the server by taking the current columns in the DS with their original columns (which are in DS2) A Sql query is then ran to add or remove any column mismatches. DS2 is then updated to use the same values as DS. Finally, DA.update(DS) is called, which updates all other modified values into the SQL server - theoretically. It is instead causing our peculiar exception.
Any help would be greatly appreciated, thanks.

I have fixed the problem I was encountering. I fixed it by adding the following two lines to my form.load.
cb.QuotePrefix = "["
cb.QuoteSuffix = "]"
Basically, I feel like a dumbass now but this solved the problem.

Related

ADODB Recordset from Access VBA to a SQL Server fails to update a Boolean field from true to false

I'm maintaining an Access 365 database (32-bit) running on devices using Access 365 Runtime (32-bit) on Windows 10 & 11. The back-end uses Microsoft SQL Server Express (64-bit), version 15.0.4198.2, on AWS RDS. For one feature, the code uses ADODB 2.8 (the VBA reference is Microsoft ActiveX Data Objects 2.8 Library) to open a Recordset, connect to a table, and modify some fields.
The code was working just fine until I included a line to switch a boolean field from true to false. After this change, the code would throw error #-2147217864 with the description Row cannot be located for updating. Some values may have been changed since it was last read.. I isolated the code to a unit test and ensured that no other lines of code changed the recordset, but the code still threw the error.
Here's the unit test with some helper functions shown but not included:
Private Sub TestRelistingDataChangeProcess()
On Error GoTo TestFail
Dim itemSku As String
itemSku = "1234"
Dim verifySql As String
verifySql = StrFormat("SELECT failedImport FROM dbo.myTable WHERE SKU = '{0}'", itemSku)
Dim rsSql As String
rsSql = StrFormat("UPDATE dbo.myTable SET failedImport = 0 WHERE SKU = '{1}'", itemSku)
ExecuteCommandPassThrough rsSql
rsSql = "PARAMETERS SKU Text ( 255 ); SELECT * FROM myTable WHERE SKU=[SKU]"
Dim cmd As ADODB.Command
Set cmd = New ADODB.Command
cmd.ActiveConnection = GetCurrentConnection()
cmd.CommandText = rsSql
Dim param As ADODB.Parameter
Set param = cmd.CreateParameter(Name:="[SKU]", Type:=adLongVarChar, Value:=itemSku, Size:=Len(itemSku))
cmd.Parameters.Append param
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.Open cmd, , adOpenDynamic, adLockOptimistic
With rs
Debug.Print "1. Setting field to TRUE."
.Fields("failedImport") = True
.Update
Assert.IsTrue ExecuteScalarAsPassThrough(verifySql)
Debug.Print "2. Setting field to FALSE."
.Fields("failedImport") = False
.Update
Assert.IsFalse ExecuteScalarAsPassThrough(verifySql)
End With
Assert.Succeed
TestExit:
Exit Sub
TestFail:
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
Resume TestExit
End Sub
Searching for information on this error led to many possibilities, not all of them in VBA. I was aware of issues with Access and SQL Server tables with nullable boolean fields, so I verified the SQL Server table had a default value for the field. I tried numerous CursorType and LockType combinations when opening the recordset. None worked.
What am I doing wrong that causes this error to be thrown? What can I do to change the code so that it works?
After serious searching and testing, I found this blog post which included this line from the [9 Nov 2009 8:49] Tonci Grgin post:
rsCustomers.Properties("Update Criteria").Value = adCriteriaKey
I didn't recognize the adCriteriaKey enum, so I searched, found, and read this MS documentation page. This enum family "specifies which fields can be used to detect conflicts during an optimistic update of a row of the data source with a Recordset object." Specifically, the adCriteriaKey value "detects conflicts if the key column of the data source row has been changed, which means that the row has been deleted."
Through some testing and debug statements, I learned the recordset I opened used adCriteriaUpdCols by default. This value "detects conflicts if any of the columns of the data source row that correspond to updated fields of the Recordset have been changed." For whatever reason, ADODB was identifying a conflict when there shouldn't be one. I wondered whether the bug had something to do with VBA using -1 as true where SQL Server uses 1, but that doesn't appear to be the case based on this SO post.
I also don't know why the previous version of code worked when changing the boolean field from false to true but not from true to false. Perhaps there is a way to trace into the ADODB code and determine exactly what's going wrong here, but I don't know how to do it yet. I've already spent HOURS on this bug, so I need to move on... :-)
As such, here's the line of code I added to make everything work:
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.Open cmd, , adOpenDynamic, adLockOptimistic
rs.Properties("Update Criteria").Value = adCriteriaKey ' <----- NEW LINE HERE
Note that this line will only work for you if your table includes a primary key and you use it in your Recordset. Also, here's another forum post showing the adCriteriaKey saving the day.
I hope this writeup makes sense to others and helps save someone in the future some time! If nothing else, it was a good exercise for me. :-)

Having trouble storing values from my database in a list in VB.NET

As the title states, I am currently trying to store values from my database in a list. In SSMS I have verified that my query pulls the data that I require, but when it comes to vb.net and putting those values into a list it appears to be failing. Below I have copied the code I am currently working with.
Try
Dim sb As New StringBuilder
With sb
.Append("SELECT TypeID FROM EnrollmentType AS a ")
.Append("INNER JOIN Enrollment AS e ON a.TypeDescription = e.EnrollmentType ")
.Append("WHERE AccountNumber = #AccountNumber")
End With
Using connEType As New SqlConnection(ConfigurationManager.ConnectionStrings("Blah").ToString)
Using cmdEType As New SqlCommand(sb.ToString, connEType)
cmdEType.Parameters.AddWithValue("#AccountNumber", strgAccountNum)
connEType.Open()
Using sdrEType As SqlDataReader = cmdEType.ExecuteReader
If sdrEType.HasRows Then
While sdrEType.Read
For i = 0 To dtEType.Rows.Count - 1
listEType.Add(dtEType.Rows(i).Item("TypeID"))
Next
End While
End If
End Using
connEType.Close()
End Using
End Using
lblSKUDescription.Text = dtEType.Rows.Count
Catch ex As Exception
Throw ex
lblSKUDescription.Text = "Oops"
End Try
Even though in SSMS my query works, here nothing is added to the list and my datatable has no rows of data. This piece of code is currently in the page load event because I figure I would want this list populated as soon as possible, I don't know if this matters.
I would also note that there is another sql connection in the page load event, however I have changed any variable names that would be conflicting, I don't know if this matters either, but felt I would mention it.
Any and all help in this matter would be greatly appreciated and if I need to supply additional information I can.
Your primary issue is that you are not actually using the sdrEType reader, instead you are just using the dtEType datatable, which hadn't been loaded. It's unclear why the For loop was necessary in the first place.
You should use a multi-line string, rather than a StringBuilder, it;s much clearer.
The Catch makes no sense, as the Throw ex wipes the stack trace, and does not run the following line. You should just remove it. If you really need the Catch, use Throw not Throw ex
If sdrEType.HasRows is unnecessary, the While will sort that out.
connEType.Close() is unnecssary, the Using will do that.
Specify parameter types and lengths explicitly.
Qualify each column in the SQL with its table alias/prefix.
Dim query As String = "
SELECT a.TypeID
FROM EnrollmentType AS a
INNER JOIN Enrollment AS e ON a.TypeDescription = e.EnrollmentType
WHERE e.AccountNumber = #AccountNumber;
"
Using connEType As New SqlConnection(ConfigurationManager.ConnectionStrings("Blah").ToString)
Using cmdEType As New SqlCommand(query, connEType)
cmdEType.Parameters.Add("#AccountNumber", SqlDbType.VarChar, 100).Value = strgAccountNum
connEType.Open()
Using sdrEType As SqlDataReader = cmdEType.ExecuteReader
While sdrEType.Read
listEType.Add(sdrEType("TypeID"))
End While
End Using
End Using
End Using
lblSKUDescription.Text = dtEType.Rows.Count

is there any way to lock a row temporarily

I am using sql server with my VB.NET application where in multiple instance of the application is run from different server (CITRIX). I am sorting and picking up one individual Row for processing and immediately marking that row as picked in a column so that other instance doesn't pick up the same row and waste time. The issue is, in between picking up the row and updating as picked, another instance of the application is picking up the row. I have been suggested for using with DB Lock but the concept is not that much clear to me like whether it will solve my problem, whether I need admin right to use it (I do not have admin right in client DB) etc. Below is the code snippet I have used.
Dim MyConnection As SqlConnection
Try
MyConnection = New SqlConnection(connString)
MyConnection.Open()
Dim tableName As String = myTableName
Dim sqlQuery As String = "Select Top 1 * from " + tableName + " where "<some condition>
Dim MyCommand As SqlDataAdapter = New SqlDataAdapter(sqlQuery, MyConnection)
Dim DS as DataSet = New DataSet
MyCommand.Fill(DS, tableName)
If DS.Tables(0).Rows.Count >= 1 Then
sqlQuery = "UPDATE " + tableName + " SET Fld = #fld where Cond1= '" + DS.Tables(0).Rows(0).Item("Cond1").ToString + "'"
Dim cmd As New Data.SqlClient.SqlCommand(sqlQuery)
cmd.CommandType = CommandType.Text
cmd.Parameters.Add("#fld", Data.SqlDbType.VarChar).Value = "Picked"
Try
cmd.Connection = MyConnection
cmd.ExecuteNonQuery()
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End If
Catch ex As Exception
MsgBox(ex.ToString)
Finally
MyConnection.Close()
End Try
I want it to make in such way that if an instance picks up a row, until it finishes updating the row, the row will not be visible to other instance with same query on the table, but other instance will continue to work with the other rows at the same time.
Two options I see:
Change your SELECT and UPDATE queries to a single UPDATE query. I didn't see where your SELECT was buying you anything.
If the SELECT is truly needed, then use a stored procedure on the database to handle the SELECT and the UPDATE on the database server side. You can lock the row during the transaction. See: Transaction Locking and Row Versioning Guide
Note that in general you should try to move your database queries to stored procedures. Not only does this reduce the amount of network traffic moving datasets back and forth, it increases the reliability, separates your database code from the UI, allows updates to the procedures without having to push new versions of the client application out and also avoids SQL injection.

vb.net ms access mdb table insert record code not works

I want to insert data into MS Access database (mdb) table. The code is shown below:
Imports System.Data.OleDb
Imports System.Data
Dim Cmd As OleDbCommand
Dim SQL As String
Dim objCmd As New OleDbCommand
Dim Con = New OleDbConnection("Provider=Microsoft.ace.oledb.12.0; Data Source=" & pth & "\database.mdb; User Id=; Password=;")
MsgBox(RichTextBox1.SelectedText)
SQL = "insert into approved ( word, approveds) VALUES ('" & RichTextBox1.SelectedText & "', " & "'YES')"
MsgBox(SQL)
Cmd = New OleDbCommand(SQL, Con)
Con.Open()
objCmd = New OleDbCommand(SQL, Con)
objCmd.ExecuteNonQuery()
Con.Close()
It shows following error message
An unhandled exception of type 'System.Data.OleDb.OleDbException' occurred in System.Data.dll
Additional information: Data type mismatch in criteria expression.
Can any one please help
Or any other procedure
First thing's first.
What do you expect " & "'YES' to mean ? Correct me if i'm wrong but you want to pass Yes as the value ? Or maybe even "Yes"(with quotes) ?
If it's the first one, only Yes inside single quotes would be enough('Yes'). For the second case, it would be '\"Yes\"'.
There are still too many issues with your code. You are using & operator to concatenate strings. Some might argue that there's no problem with using & but look at the code and tell me what's your first expression? Well, my first expression was :
That looks ugly!
So, instead of concatenation, you could simply format the string. Of course the results are the same, yet string formatting would look cleaner and more professional. Here's a sample :
Dim x as String = "My Name Is {0}."
Dim MyName As String = String.Format(x, "Zack Raiyan")
Now comes MsgBox(SQL). Well, i don't need a second guess for this, you are using this line to see if your sql statement is as expected. If that's the case, why not just put a breakpoint ?
Now, let's talk about your variable declarations. Why declare a variable without initializing it with any instance but later on, just after a few lines of code, initialize it when this could've been done in the first place ? Sample :
Dim x As New ObjectX
Instead of
Dim x As Object
.....
.....
x = New Object()
Finally, if you are getting to frustrated reading all these suggestions, let's talk about the main culprit here, your sql statement.
insert into approved ( word, approveds)
A space before and after the parenthesis may not make any difference but why use them at all? You may be new in programming but understand this:
You would spend 20% of your time in writing code and 80% of your time in maintaining it.
So, always write clean, simple & reusable codes.
Moving on....
VALUES ('" & RichTextBox1.SelectedText & "')
Don't do this! Instead pass parameters and then pass values to them. A quick example :
Dim sql = New SqlCommand("Insert Into TableX(Column1)Values(#Column1Val)", MySqlConnection)
sql.Parameters.Add("#Column1Val", SqlDbType.VarChar).Value = "XYZ"
There's a shorter way tho :
sql.Parameters.AddWithValue("#Column1Val", "XYZ")
but only use it when you know that you are passing a value of the same data type as the column you are passing it to.
I explained as much i could. I hope you understand your mistakes and also hope that you don't fail to see how my answer addresses the exception you are getting. If you are still unclear, leave a comment and i would be happy to help.
Just a small addition to #zack raiyan 's code. That is the Using block which will close and dispose your connection. This is important with connection objects because they use unmanaged code.
I am guessing that since your error is a data type mismatch that the problem might be with the approveds column. If this is a Yes/No column then it should be safe to use True for the value.
Check the data types of the 2 parameters in your database table and adjust the code appropriately.
Private Sub AddRecord(pth As String)
Dim SQL = "insert into approved ( word, approveds) VALUES (#word, #approval)"
Using Con As New OleDbConnection("Provider=Microsoft.ace.oledb.12.0; Data Source=" & pth & "\database.mdb; User Id=; Password=;")
Dim Cmd As New OleDbCommand(SQL, Con)
Cmd.Parameters.Add("#word", OleDb.OleDbType.VarChar).Value = RichTextBox1.SelectedText
Cmd.Parameters.Add("#approval", OleDbType.Boolean).Value = True
Con.Open()
Cmd.ExecuteNonQuery()
End Using
End Sub

Verify credentials from database

I had confusion with my code:
Dim sqladapter As SqlDataAdapter = New SqlDataAdapter()
Dim sqlcmd As SqlCommand = New SqlCommand()
sqlcmd = New SqlCommand("SELECT login, pass from Table1 where login=" & login.Text & "and pass='" & password.Text.ToString() & "';", connect)
Dim dr As SqlDataReader = sqlcmd.ExecuteReader()
Dim dt As DataTable = New DataTable()
dt.Load(dr)
If (dt.Rows.Count = 1) Then
'Display welcome page or do some action here.
Now, my question is, is there any other way of doing Rows.Count==1 . I'm feeling that it is very wrong and makes no sense at.
How do you verify from database that a user has only one valid record in table other than counting rows.
Thanks in Advance :)
(Please ask me before reporting question)
You have two problems, one is called Sql Injection and you have already numerous links that explain why is really bad. Another one is the plain text password stored in your database. This is a big security concern because everyone that has the possibility to look at your database could see the passwords of your users. (The gravity of this, of course, is linked to the nature of your application but cannot be downplayed) See this link for an answer on how to hash a string (a password) and get its encrypted version to store in the database instead of the plain text.
Finally the code you use could be changed to avoid both the SqlDataAdapter and the DataTable.
Just use an ExecuteScalar against an IF EXIST query that return just 1 if the user/password exists or zero if not
Dim cmdText = "IF EXISTS(SELECT 1 FROM Table1 WHERE login = #log AND pass = #pwd) " & _
"SELECT 1 ELSE SELECT 0"
using connect = new SqlConnection(connectionstring)
using sqlcmd = New SqlCommand(cmdText, connect)
connect.Open()
sqlcmd.Parameters.AddWithValue("#log", login.Text)
sqlcmd.Parameters.AddWithValue("#pwd", password.Text) ' <- Subst with a call to an hash function
Dim exists = Convert.ToInt32(sqlcmd.ExecuteScalar())
if exists = 1 Then
'Display welcome page or do some action
else
end if
End Using
End Using
There is only one way to answer to the question and its to count rows. The different solution would be to count them in database. For example you could write stored procedure that takes username and password and returns boolean this way you would drag less data.
As a side note there is potential sql injection in your code. You should not store clear password in database. You should return the whole row and match hash of the password from database to the hash of the paasword that you get from UI.

Resources