I've been researching it and have found many similar cases... but none exactly the same. I've tried a lot of different resolutions in the previous cases mentioned and none of them fixed this one.
I just recently did a SQL Server Migration and am now troubleshooting the issues that have sprung up with the new SQL back end. This is the last one that I can not figure out:
I have a split form with a check box control, bound to a SQL Server View through a DSN-Less connection. Before the migration the Bound Access Query could not be updated... so I programatically updated the forms checkboxes through VBA using two different methods:
Was a check all/ check none checkbox which when clicked checked or unchecked all of the checkboxes in the datasheet.
Gave the ability for the user to check or uncheck each individual checkbox by using the checkboxes Mouse_Down Event.
Here is the code for each of the two methods:
' Check All/ Check None
Dim rsSelect As DAO.Recordset
Dim rsUpdate As DAO.Recordset
Dim SQL As String
Dim CurrDb As Database
Dim currFilter As String
On Error GoTo chkSelect_Click_Error
' Capture current filter
If Me.FilterOn Then currFilter = Me.Filter
Set rsSelect = Me.RecordsetClone
Set CurrDb = CurrentDb
rsSelect.MoveFirst
Do While Not rsSelect.EOF
SQL = "SELECT * FROM tblTimesheet WHERE [TimesheetID] = " & rsSelect("TimesheetID")
Set rsUpdate = CurrDb.OpenRecordset(SQL, dbOpenDynaset, dbSeeChanges)
If Not rsUpdate.EOF Then
If Me.chkSelect Then
With rsUpdate
.Edit
rsUpdate("TimesheetSelect") = True
.Update
End With
Else
With rsUpdate
.Edit
rsUpdate("TimesheetSelect") = False
.Update
End With
End If
End If
rsSelect.MoveNext
Loop
rsUpdate.Close
rsSelect.Close
Me.Requery
If currFilter > "" Then
Me.Filter = currFilter
Me.FilterOn = True
End If
If Me.chkSelect Then
Me.lblSelect.Caption = "Select None"
Else
Me.lblSelect.Caption = "Select All"
End If
On Error GoTo 0
Exit Sub
chkSelect_Click_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure chkSelect_Click of VBA Document Form_frmTimesheetSummary"
And Secondly:
' Check/ Uncheck Individual Checkbox
Dim rsUpdate As DAO.Recordset
Dim SQL As String
Dim CurrDb As Database
Dim currFilter As String
' Capture current filter
If Me.FilterOn Then currFilter = Me.Filter
Set CurrDb = CurrentDb
SQL = "SELECT * FROM tblTimesheet WHERE [TimesheetID] = " & Me.TimesheetID
Set rsUpdate = CurrDb.OpenRecordset(SQL, dbOpenDynaset, dbSeeChanges)
If Not rsUpdate.EOF Then
If Me.TimesheetSelect Then
With rsUpdate
.Edit
rsUpdate("TimesheetSelect") = False
.Update
End With
Else
With rsUpdate
.Edit
rsUpdate("TimesheetSelect") = True
.Update
End With
End If
End If
rsUpdate.Close
Me.Form.Requery
'Me.Repaint
Me.Refresh
If currFilter > "" Then
Me.Filter = currFilter
Me.FilterOn = True
End If
Both of these procedures worked with an Access back end... but the "Check Individual" procedure refuses to work now. When I check a checkbox it does update the SQL Backend... but the control itself refuses to update the new status... I of course have tried Requery, but also Repaint and Refresh and it refuses to update unless I completely close the form and reopen it again.
The real kicker in all this is that the Check All method still works! I've spent hours on this and am hoping to get some fresh eyes on it because it should be working if the backend is updating!!
ADDITIONAL NOTES ADDED LATER: In response to some of the great reasoning below I feel I should include this additional notes:
I am using SQL Server 2012 and the SQL Server Native Client 11.0 Driver for my connection string.
I am using Microsoft Access 2010 32 bit
The SQL Server field is a bit I've removed all nulls and set allow nulls to 'no' with a default of 0
Some things come to mind:
1) You can replace the whole rsUpdate construction in the second procedure by this:
SQL = "UPDATE tblTimesheet SET TimesheetSelect = " & _
IIf(Me.TimesheetSelect, "0", "-1") & _
" WHERE TimesheetID = " & Me.TimesheetID
CurrDb.Execute SQL, dbSeeChanges
It depends on your TimesheetSelect datatype whether you should use "-1" or "1".
2) Me.Form.Requery should be Me.Requery.
3) If it still doesn't work, adding a TIMESTAMP column to tblTimesheet might help Access recognize that the record was changed. This is generally a good thing to have, but shouldn't be necessary.
Recommended reading: https://stackoverflow.com/a/2861581/3820271
tblTimesheet does have a primary key, doesn't it?
If Andre's suggestions don't solve it, instead of Me.Requery, try resetting the form's recordsource. Me.RecordSource = .
You don't say what version of SQL Server you are using or which ODBC driver. Make sure you are using the correct ODBC Driver for your version of SQL Server and not the default 'SQL Server' driver.
Thanks to all who posted... the answer that ended working for me in this case was ditching the split form... I had heard it suggested that split forms can be problematic at times so redesigned the form in a standard Parent/ Subform setup which completely resolved the issue.
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 found #mwolfe02's Mike Wolfe's advice to do in-place Form requires without headaches or fuss. Cool. Looks easy enough, cuts down on extra coding required. Except...it doesn't work.
Every time, every form, no matter what, I get error 3251: "Operation is not supported for this type of Object". Every time. Doesn't matter how the Form is bound, whether via me.recordsource = "something", set me.recordset = somerecordset, statically assigned in the Form Properties, etc.
Forms are set to Dynaset, passthrough (when used) are opened as dbOpenDynaset, etc.
I am able to use me.recordsetclone just fine in these (in fact, that's what I'm doing now to get the rowid). What am I doing wrong? or is there something wrong? Or is this just not usable in practice with SQL Server as the source?
For reference, I'm using O365 Office 64 bit, Ver 16, and SQL Server as a backend, but some tables are loaded locally as temp tables.
[Edit]
When setting the form's record source, I do it like this:
' (in the form open)
me.recordsource = SendSQLReturnData (SQLString) ' This works great, and I know the SQL functions.
(later in an OnPress from button:
me.recordset.requery ' <= Does not work.
' (SendSQLReturnData)
Public Function SendSQLReturnData(ByRef SQLString As String, _
Optional ByRef ReturnTable As String = PassthroughQuery, _
Optional DeleteExist As Boolean = False, _
Optional NoChange As Boolean = False) As String
'Sets up a passthrough query, hands back the name of the string via query.
Dim qdf As DAO.QueryDef
Dim rstReport As DAO.Recordset
Dim dB As DAO.Database
Set dB = CurrentDb
If DoesQryExist(ReturnTable) = False Then
'this handles the case where the table wasn't set up already either by accident or didn't need to be previously.
Set qdf = dB.CreateQueryDef(ReturnTable)
Else 'Assume the only other case for "Does it exist" is true, so don't bother checking
If NoChange = False Then
Set qdf = dB.QueryDefs(ReturnTable)
Else
GoTo Exit_Here
End If
End If
With qdf
.Connect = dB.TableDefs(BackEndKeyTbl).Connect
.SQL = SQLString
.ReturnsRecords = True
.OpenRecordset , dbOpenDynaset
End With
Exit_Here:
SendSQLReturnData = ReturnTable
qdf.Close ' Turning these on/off doesn't change result.
Set qdf = Nothing
Set dB = Nothing
End Function
This will not work when the Form or Report recordsource is a passthrough query.
I have an Access application (Access Front-end, SQL backend using Linked tables) and I'm having this issue:
The user enters in a part number. Then they enter in a quantity. New business logic says I have to check this part number against a table, to see if we have quoted it to a customer within the past year. If we have, we can quote it now; if not, we have to reject the part.
When the program was originally written, they used a datasheet format to allow the user to copy from Excel a list of parts, paste them into the datasheet, and then copy and paste in a list of quantities. Once the quantity is entered/pasted for a line, that line is processed.
If I manually enter in a part number, then the quantity, the needed code checks to see if the part number has been quoted within the last year, and everything is peachy.
But if I copy and paste in a list of parts, the first time through the process it works fine; but every other time fails.
Here's the basic code:
Dim cn21 As ADODB.Connection
Set cn21 = New ADODB.Connection
Dim strsql21 As String
Dim cm21 As New ADODB.Command
Dim rs21 As New ADODB.Recordset
' gblODBCString = "ODBC;Description=PartsPortalsSql;DRIVER=SQL Server;SERVER=db-TEST-partsptl-primary;Trusted_Connection=Yes;APP=Microsoft Office 2010;DATABASE=PartsPortalSQL;"
cn21.Open gblODBCString
cm21.ActiveConnection = cn21
' All I want to know is how many records there are for this part within the last year...
strsql21 = "Select count(*) from tblQuoteDetail tqd INNER JOIN tblQuotes tq on tqd.quoteid = tq.quoteid WHERE " _
& " tqd.qdetailpartno = '" & Me.QDetailPartNo & "' AND tq.quotesentdate >= '" & OneYearAgo & "' AND tqd.qdetailunitprice > 0"
cm21.CommandText = strsql21
Set rs21 = New ADODB.Recordset
Set rs21 = cn21.Execute(strsql21, varparams, adCmdText)
If rs21(0) = 0 Then ' nothing found! can't be escalated...
blah blah blah...
end if
' done with this part... clean up
Set cm21 = Nothing
rs21.Close
Set rs21 = Nothing
Set cn21 = Nothing
Then it will return here when it gets to the next part...
But if i do it manually, it works fine. But when it is running through the loop of parts, it gets to the:
Set rs21 = cn21.Execute(strsql21, varparams, adcmdtext)
line
and takes about 30 seconds to 'process/time out', and then the rs21(0) returns "Run-time error '3265': Item cannot be found in the collection corresponding to the requested name or ordinal."
I've verified every field in the sql query is correctly populated. I've never run into this issue before.
Other things I've attempted - use DAO instead of ADODB... no luck...
If anyone has any suggestions I'm all ears... short of blowing it up... I'm in the testing phase of a replacement version, that doesn't use that copy/paste functionality. But that's still a month or two away from production.
Thanks
Access 2016, Linked tables to Microsoft SQL Server 2016 I believe...
Windows 10 64 bit
Also running on a VPN, connected to a VDI.
I htink youre using the wrong commands to check for recordsets. I could be wrong as I dont use ADO as much as I should, but here is my template for it if you thin itll help
'needs the MSO AtiveX Data Objects Library
Dim vbSql As String, cnnstr as string
Dim cnn As ADODB.Connection
Dim rs As New ADODB.Recordset
vbSql = "SELECT ;"
Set cnn = New Connection
cnnstr = ""
cnn.Open cnnstr
rs.CursorLocation = adUseClient
rs.Open vbSql, cnn
cnn.Close
Set cnn = Nothing
So I have this application and I moved all local tables to SQL Server using upsizing, now they are currently linked tables. I'm able to access tables and forms related to tables can be accessed with no problems. But when I programmatically fetch a record, or perform a sql operation in VBA script, a SQL Server Login prompt pops up asking me to enter in the SQL Authentication login to access the database.
I followed this link here:
https://support.microsoft.com/en-us/kb/177594
Where this is my end code:
Dim db1 As Database
Dim db2 As Database
Dim rs As Recordset
Dim strConnect As String
Set db1 = OpenDatabase("C:\Workspace\ms1.mdb")
strConnect = UCase(db1.TableDefs("dbo_TableA").Connect) & ";UID=User1;PWD=Password1"
Set db2 = OpenDatabase("", False, False, strConnect)
db2.Close
Set db2 = Nothing
Set rs = db1.OpenRecordset("dbo_TableA")
rs.Close
db1.Close
Set rs = Nothing
Set db1 = Nothing
DoCmd.RunCommand acCmdSaveRecord
'Sql Server login prompt pops up after running the below code;'
If DCount("*", "TableA", "[ColA] = [forms]![FRM_LOGS]![USER]") = 0 Then
MsgBox "User ID not found - contact HelpDesk", vbCritical
DoCmd.Quit
Exit Sub
End If
The DCount is triggering the SQL Server Login Prompt. I need this prompt to go away. If I open up a form, query, report, anything where the access object is bound to the data, I get no message. It ONLY happens in VBA when I'm trying to access the data object.
Edit! I did find the culprit. I deleted the linked table to the TableA in sql server, and I relinked it again, and clicked the Save password checkbox. I did this before, and it didn't work. Did it again, and it fixed everything. Not sure why this didn't work the first time. I marked the below as an answer because that did solve the problem given the circumstances.
Not sure what you're doing here with two database connections and using DCOUNT on an internal table?
It looks like your database connection has linked tables that have stored passwords
Why not just use your recordset that works to check for a valid user?
Set db1 = OpenDatabase("C:\Workspace\ms1.mdb")
Set rs = db1.OpenRecordset("SELECT [ColA] FROM [dbo_TableA] WHERE [ColA] = """ & [forms]![FRM_LOGS]![USER] & """")
if rs.EOF Then
MsgBox "User ID not found - contact HelpDesk", vbCritical
DoCmd.Quit
Exit Sub
End If
rs.Close
db1.Close
Set rs = Nothing
Set db1 = Nothing
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!