I am trying to recover deleted rows by selecting rows in this picture, there are 3 records, so I deleted the 2
they will then show up here in the archive
but when I try to recover, instead of one by one, they will be recovered instantly. as it loads, the recovered records will then be back to the first picture]
which is the first 1 and this is my code for the recover:
Private Sub Recover_Click(sender As Object, e As EventArgs) Handles Recover.Click
Dim connString As String = ConfigurationManager.ConnectionStrings("dbx").ConnectionString
Using conn As New SqlConnection(connString)
Dim command As New SqlCommand("UPDATE [EmmeSubic].[dbo].[UserDetails] SET isDeleted = NULL where isDeleted = 1", conn)
' command.Parameters.Add("#user_id", SqlDbType.Int).Value = Driverlist.tbxUser_id.Text
conn.Open()
If MessageBox.Show("Are you sure you want to recover?", "Information", MessageBoxButtons.YesNo, MessageBoxIcon.Information) = DialogResult.Yes Then
command.ExecuteNonQuery()
For Each row As DataGridViewRow In DeletedUserTable.SelectedRows
DeletedUserTable.Rows.Remove(row)
Next
MessageBox.Show("The User is successfully Recovered!", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information)
conn.Close()
End If
End Using
End Sub
UPDATE [EmmeSubic].[dbo].[UserDetails] SET isDeleted = NULL where isDeleted = 1 undeletes every record that was ever deleted. You need to also specify the according ID.
Your query has several other issues:
I personally would never include the database name in a query. Doing so prevents you from running your application against a test or integration database with different database name, which I always recommend to do - that way the database knows whether it is the production one or not even after copying the productional database over the test database. Why would the database care? There could be different behaviors in sending mails, different path definitions for imports and exports to other systems etc.).
Why is the deleted flag NULL when not set and not 0? In my humble opinion, it should be defined NOT NULL and be defaulting to 0.
Related
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. :-)
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.
I'm sure my question has been answered many times on the internet, but I couldn't find exactly what I was looking for.
I'm working on VB.NET and my database is a SQL Server Compact .SDF file. The following is my method of opening the database.
Private Shared Sub OpenDatabase(ByVal tablename As String)
If _DBLoaded Then Return
'// open database connection
conn = New SqlCeConnection("Data Source = giadatabase.sdf")
conn.Open()
'// create command for making extracting data
cmd = conn.CreateCommand
cmd.CommandText = "SELECT * FROM [" & tablename & "]"
'// setup database adapter
da = New SqlCeDataAdapter(cmd)
'// create command for inserting/updating database
cb = New SqlCeCommandBuilder(da)
'// load dataset
ds = New DataSet()
da.Fill(ds)
'// get the relevant table
dt = ds.Tables(0)
_DBLoaded = True
End Sub
I run this sub when my application starts. I believe that database needs to be opened just once. Constantly reopening of database will cause performance problems to my application (correct me if I'm wrong).
For loading data in my list object I use the following:
Public Shared Function GetList() As List(Of DatabaseListObject)
OpenDatabase("TestTable")
'// Make a list of items in database
Dim ret As New List(Of DatabaseListObject)
For Each dRow As DataRow In dt.Rows
ret.Add(New DatabaseListObject(dRow("ID"), dRow("LongName"), dRow("ShortName")))
Next
Return ret
End Function
So my GetList function ensures database is already open, and database is always opened once a lifetime of my application. My list object is filled with data from the above function.
This is how I make changes to my database:
Public Shared Function AddItem(LongName As String, ShortName As String) As DatabaseListObject
'// Make changes
Dim row = dt.NewRow()
row("LongName") = TimeOfDay.ToString
row("ShortName") = ShortName
dt.Rows.Add(row)
da.Update(ds, dt.TableName)
Dim newcmd = conn.CreateCommand
newcmd.CommandText = "SELECT ##IDENTITY;"
Dim newID As Integer = newcmd.ExecuteScalar()
Dim item As New DatabaseListObject(newID, LongName, ShortName)
Return item
End Function
Now I assume database is correctly updated from the above code. The ID column in my table is the autonumber. Problem occurs when I call the GetList function after adding a row. System throws error that the newly added row's ID column is NULL. Whereas it should be automatically added number. When I restart the application, i.e. the database is opened from scratch, then the GetList shows the autonumber properly.
Obviously the table's ID column is not getting filled-in with the autonumber when I'm adding a new row. So I need suggestions here. Should I always open the database from scratch whenever I call the GetList (which will be called frequently in my app). If not the entire database then which codes should be called at least to properly refresh the table without causing much performance problems to the application.
SELECT ##IDENTITY will only Work on the same open Connection object, and the DataAdapter opens and Closes its own connection, you must use plain ADO.NET (cmd.ExecuteNonQuery) or implemet extra code as described here: http://blogs.msdn.com/b/bethmassi/archive/2009/09/15/inserting-master-detail-data-into-a-sql-server-compact-edition-database.aspx
I am doing some maintenance work on a linked-table application in Microsoft Access 2010 and experiencing this little gem of a problem. The application is linked to a SQL Server 2008 database. The application has a main form that allows a user to choose a combination of park code and resource and pops up an edit form for the details of that particular combination. If that combo doesn't exist in the database, the application inserts a new record in, but the issue is that 2 records get inserted.
Here's the seat of the problem code, it gets called when I need to insert a new record in a details popup form:
Private Sub New_Rec(unit_code As String, resource As String, sql As String)
DoCmd.RunSQL ("INSERT INTO PARK_RESOURCES (unit_code, resource, sensitivity) VALUES " _
& "('" & unit_code & "','" & resource & "','public')")
'Force an explicit save
'http://www.pcreview.co.uk/forums/update-cancelupdate-without-addnew-edit-t1150554.html
If Me.Dirty Then
Me.Dirty = False
End If
Me.RecordSource = sql
End Sub
Creating a "new" record results in 2 records getting inserted into the Recordset. It doesn't seem to matter if I move the explicit save code before or after setting the RecordSource. In either order (and stopping after either) produces 2 new records inserted in the database (verified by querying in SSMS).
When I set the RecordSource property and step through the code, the event chain looks like: Me.RecordSource = sql --> Form_BeforeUpdate() --> Form_AfterUpdate() --> Form_After_Insert() --> Form_Current(). The duplicate is not present at the close of BeforeUpdate, but by the time I get to AfterUpdate, the duplicate has already been inserted. What happens between BeforeUpdate and AfterUpdate that causes this to happen?
According to MSDN, the order is: BeforeInsert → BeforeUpdate → AfterUpdate → AfterInsert. They also state that setting the value of a control through Visual Basic doesn't trigger these events. But when I update the RecordSource in code, the last 3 events certainly fire; BeforeInsert is the only one that a step-through doesn't stop on.
As per Daniel Cook's request, here is the calling code.
Private Sub Form_Load()
On Error GoTo Err_Form_Load
Me.KeyPreview = True
If Not IsNull(Me.OpenArgs) Then
ProcessOpenArgs (Me.OpenArgs)
Me.lblHeader.Caption = Me.unit_code & ": Resource - " & Me.resource
Else
Me.lblHeader.Caption = "Information Needs"
End If
... (error trapping)
End Sub
And the ProcessOpenArgs sub (OpenArgs get set as "park;resource"):
Private Sub ProcessOpenArgs(open_args As String)
On Error GoTo Err_ProcessOpenArgs
Dim Args() As String
Args = Split(open_args, ";")
Me.unit_code = Args(0)
Me.resource = Args(1)
'Check to see if there are records in the database for current unit/resource combo'
Dim rs As DAO.Recordset
Dim sql As String
sql = "SELECT * FROM PARK_RESOURCES " & _
"WHERE resource='" & Me.resource & "' AND unit_code='" & Me.unit_code & "'"
Set rs = CurrentDb.OpenRecordset(sql, dbOpenDynaset, dbSeeChanges)
'if there aren''t, create a new record'
If (rs.RecordCount = 0) Then
New_Rec Me.unit_code, Me.resource, sql
Else 'go to the current listing'
Me.RecordSource = sql
End If
Exit_ProcessOpenArgs:
Exit Sub
Err_ProcessOpenArgs:
MsgBox Err.Number & Err.description
Resume Exit_ProcessOpenArgs
End Sub
I will continue to comb through the event documentation and as a last resort I may go totally nuts and just stick every possible event in my VBA code and step through them, but does anyone know what could be happening to cause the duplicates?
In your ProcessOpenArgs, the If (rs.RecordCount = 0) Then line could be a problem unless you first use rs.MoveLast - see here
When I'm setting Me.unit_code and Me.unit here:
Private Sub ProcessOpenArgs(open_args As String)
On Error GoTo Err_ProcessOpenArgs
Dim Args() As String
Args = Split(open_args, ";")
Me.unit_code = Args(0)
Me.resource = Args(1)
the code is creating 1 record and then New_Rec inserts a second record in the DB. When the Form automatically Requeries after Me.RecordSource = sql, it sticks the first record (created by the Me.xxx = yyyy statements in ProcessOpenArgs into the DB too and then pulls both back out to the Form Recordset. That's where the double insert is coming from.
In order to correct it, I changed Me.unit_code and Me.resource to local subroutine variables l_unit_code and l_resource and used those instead in ProcessOpenArgs. That took care of this problem as well as a second problem that I had with records form one resource type bleeding into other resource types.
Thanks all for the assist!
I am making a booking system application for a sports club. In my SQL Server database, I have two tables; one is named Memberships and the other is named Bookings.
Now, what I want is to allow a new booking for only those customers who have a membership. In the database I have joined both tables by the cust_id attribute where the Memberships table is parent and Bookings table is child. I want that when a new booking is tried, then it should first check if the customer ID is present in database. If it is present, then the booking should be allowed, otherwise not. But, my code shows an error first and then allows the booking
Here is my code for this:
Dim i As Integer
Dim str2 As String ' defines string variable for taking select query
str2 = "select Memberships.cust_id, booking_date, booking_time, booking_duration, game, poolno, courtno, tableno from Bookings, Memberships where Memberships.cust_id = Bookings.cust_id"
i = -1
Dim cmd2 As New SqlCommand(str2, con) 'defines a new sql command with str2 as query string and con as connection string
con.Open() 'sets the connection state to open
Dim bookchk As SqlDataReader = cmd2.ExecuteReader 'Defines and initiates the datareader to read data from database using cmd2 command
While bookchk.Read()
If Me.MskdTxtCustId.Text = bookchk("cust_id") Then
i = 1
End If
End While
bookchk.Close()
con.Close()
If i = -1 Then
MessageBox.Show("Error")
Exit Sub
End If
Why dont you just ask for the customers membership number before they can make a booking?
Otherwise you could make membership number a required field.
I could understand if you'd got as far as checking the database if it was a case of checking if the membership number they gave was valid, otherwise check for required data before connecting to a database.
Try this:
Dim sql As String
Dim customerIDyouWantToCheck As String
Dim i As Integer
i = 0
sql = "select cust_id from Memberships where Memberships.cust_id = '" & customerIDyouWantToCheck & "'"
Dim cmd2 As New SqlCommand(str2, con)
con.Open()
Dim bookchk As SqlDataReader = cmd2.ExecuteReader
If bookchk.HasRows() Then
i = 1
Else
i = -1
EndIf
I'm assuming you know the ID of the customer who you want to make a booking for. Otherwise I'm not sure what you're doing.
The best way to deal with anything like this is to ensure the database returns what you want first. Have you tried the run the query directly against sql-server?
What you really need is to check the customers ID in the Membership table first, and once the correct customer is established separately go ahead an insert into the Bookings table. These are both atomic transactions.
I think you are making this simply problem too complex.
Loading all booking/membership combinations and looping through them to find a certain customer ID is bad practice.
It is better to query for the desired customer ID directly. Plus, you should use SQL parameters instead of string concatenation to pass the customer ID to the query.
Before:
str2 = "select [...] where Memberships.cust_id = Bookings.cust_id"
i = -1
Dim cmd2 As New SqlCommand(str2, con)
After:
str2 = "select [...] where Memberships.cust_id = Bookings.cust_id and Bookings.cust_id = #CustomerID"
i = -1
Dim cmd2 As New SqlCommand(str2, con)
cmd2.Parameters.Add(New SqlParameter("#CustomerID", SqlDbType.Int).Value = Me.MskdTxtCustId.Text)
Regarding your actual problem:
but my code either shows error first and then allows the booking
As you didn't show the code that does the booking and how it is connected to the code you showed, we can't tell how you use the result from the code you posted to determine if the booking is done or not.
You should wrap the code that you posted in a method that returns true or false, depending whether the booking is allowed or not.
'the CustomerHasMembership() method contains the code you posted and
'returns true when the customer DOES have a membership
If CustomerHasMembership(Me.MskdTxtCustId.Text) Then
DoBooking()
End If