It's been a long time since I've had to do any development in Access, so hoping I can get some help. I have a split Front End/Back End solution that I've built. The Back End resides on a server, the front end gets copied down to user's desktops (and they use runtime Access 2013). I'm using Access with VBA and ADO connections/recordsets in order to do all record actions (Select, inserts, updates mostly).
Two intermittent issues have cropped up and I'm at a loss - this is one of them. From time to time, some users will get the error "Could not use "(back end Filename)"; file already in use." (where (back end filename) is my back end db name & location". When users get this message, they close out, re-open and try the same data entry and it works without a hitch. Here's the code:
Private Sub SetProblemCode()
On Error GoTo ErrorHandler
strSQL = "SELECT Problem_Code_ID, Problem_Code, Problem_Description FROM Problem_Code ORDER BY Problem_Description"
con.Open strConString
rstProblemCode.CursorLocation = adUseClient
rstProblemCode.Open strSQL, con, adOpenForwardOnly, adLockReadOnly
cboProblemCode.RowSourceType = "Table/Query"
Set cboProblemCode.Recordset = rstProblemCode
rstProblemCode.Close
con.Close
Exit Sub
ErrorHandler:
CriticalError Err.Description, Err.Number, Me.Name, "SetProblemCode"
End Sub
The rst and con objects are defined at the global level, a practice I've used in other solutions before but I'm questioning if that's some of the problem. I'm also questioning the cursor location, type and lock type I'm using, although it seems correct - I'm not altering data, just copying a recordset to the Access combo box.
I'm hesitant to make sweeping changes when it seems like the user closes out and tries again and it works just fine. Any thoughts?
Related
I have a number of Excel dashboards running in my factory that refresh each minute, by connecting to a local Access Db and running a number of queries.
The Db itself has ODBC connections to a couple of different SQL database tables. They run fine most of the time but I have a problem with random 3146 ODBC--call failed errors popping up.
I have worked with my IT people and have not really been able to nail down the root cause.
I can click OK on the error popup and the dashboard may refresh fine for anther day or more or it may pop the 3146 error again 10 minutes later. There does not seem to be any real consistency to it.
I have read several posts about missing primary keys in the linked tables as to root cause. That may be true here as well but unfortunately these tables are from a third party vendor and I cannot modify them without creating issues with their software functionality.
Short of an actually fix for the root cause, I am wondering if there is a way through my VBA that I can automate clicking the OK button on the 3146 error popup so that this error is automatically acknowledged and the dashboard can go on about it's business.
One way to handle the situation is to trap the error. I can only suggest a general approach, one that I use in similar situations. Without knowing more about your app, it is hard to be very specific. This answer assumes you are using ADO to connect to Access, and pulling back a Recordset to populate the Dashboard. It further assumes you are not trapping errors currently. Here is a template to illustrate the idea:
Public Sub Dashboard()
On Error GoTo error
Dim e As ADODB.error
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Set cn = "however you are connecting"
Set rs = "however you are retrieving data" 'assuming an error with this line
Do While Not rs.EOF
rs.MoveNext
Loop
cleanup:
'any code that always has to happen
Exit Sub
error:
If Err.Number = 3146 Then
'do nothing and continue where you left off
Resume Next
End If
'possibly check connection errors, too
For Each e In cn.Errors
If e.Number = 3146 Then
'do nothing and continue where you left off
Resume Next
End If
Next
'show error and exit sub (which is what it is doing now)
MsgBox Err.Description
Resume cleanup
End Sub
Here's another option if you don't care to catch a specific error:
Public Sub Dashboard()
Dim rs As ADODB.Recordset
On Error Resume Next
Set rs = "however you are retrieving data" 'assuming an error with this line
On Error GoTo 0
Do While Not rs.EOF
rs.MoveNext
Loop
End Sub
A software management system let's call it "MainSystems" takes orders, creates invoices, etc. MainSystems uses multiple .DBF files as a database.
There are several third party applications that retrieve certain info (all written in VBA). For example, I created an Excel VBA macro which pulls debt and applied credits from the .dbfs and generates a batch file to upload to a merchant.
I have the VFPOLEDBSetup driver installed to do this. It has run for the past year with no errors.
MainSystems did a system update and now third party applications return errors.
Excel returns
"Run-time error '-2147467259(80004005)': DIRECTORY\FILE.dbf is not a table."
at line rs.Open sql, con
and Visual Fox applications solely returns
"Not a table."
These applications all worked before the update, only thing I noticed that might be different is within the folder with the .dbfs there are .cdx created the day of the updates and am wondering if this could have caused the error. If so, is there a solution to fix this?
I'm not very knowledgeable with databases.
'Changing directories
DBFFolder = "G:\DIRECTORY\"
FileName = "File.DBF"
On Error Resume Next
'Create the ADODB connection object.
Set con = CreateObject("ADODB.connection")
'Check if the object was created.
If Err.Number <> 0 Then
MsgBox "Connection was not created!", vbCritical, "Connection error"
Exit Sub
End If
On Error GoTo 0
'Open the connection.
con.Open "Provider=vfpoledb;" & "Data Source=" & DBFFolder & FileName & ";Collating Sequence=machine"
'Create the SQL statement to read the file.
'Note that the filename is used instead of the table name.
sql = "SELECT * FROM " & Left(FileName, (InStrRev(FileName, ".", -1, vbTextCompare) - 1))
On Error Resume Next
Set rs = CreateObject("ADODB.recordset")
'Check if the object was created.
If Err.Number <> 0 Then
MsgBox "Connection was not created!", vbCritical, "Connection error"
Exit Sub
End If
On Error GoTo 0
'Open the recordset.
rs.Open sql, con
Expected result: Access the DBF table and copy info.
UPDATE
Found the cause of the error. MainSystems added columns to the table making it surpass 256 columns. (262) causing the Microsoft driver to not recognize it as a table. Is there a workaround?
Although I have and use for supporting a system requiring database querying but can not be via a "Server" product, there is a product from SAP called Advantage Database. There is an Advantage LOCAL Server and Advantage Database Server. The local version runs based on a set of libraries and is more file-sharing over network capabilities. Works similar with OleDb connections, querying, parameterize queries. It works with VFP tables, but also has its own support to recognize more than the 255 columns. I don't know if this is the answer you need, but MIGHT work. It also apparently supports reading DBase IV files as well.
As for finding the library download, that might be a bit tricky. I don't remember the last time I had to look for the "LOCAL" database version vs server (where they make their sales)
Hope this guides you in a direction of possible resolution.
I've recently moved a db built in Access 2010 to Access 2013. It was working fine for a while then it suddenly started crashing (i.e. not responding) whenever a certain table is updated using VBA. I can run the query on its own without any problem but can't call it in VBA. Below is an example of the code that would cause a crash. Any attempt to update this table causes it to crash:
Sub ShipOrder(OrderID As Long)
Dim strSQL As String
On Error GoTo ErrorHandler
strSQL = "UPDATE Orders SET StatusID = 20 WHERE ID = " & OrderID
CurrentDb.Execute strSQL, dbFailOnError
Exit Sub
ErrorHandler:
MsgBox Err.Description
End Sub
I've already moved all tables, forms, etc. to a new database just in case the old one was corrupted but the problem persisted.
Any suggestions on what is causing this error are greatly welcome!
Thanks.
Sound like your linked DB is the problem..
Did you try-
docmd.RunSQL ""
may be CurrentDb on CurrentDb.Execute is the problem...
good luck..
Background
I develop a number of tactical data capture tools all of which use the same approach of a split front and back end as below:
Excel VBA forms based front end (using ADO to connect to the back end)
Access 2007 (accdb) database as a back end
My preference would be to use SQL Server as a back end but this is not possible due to restrictions that I am not able to solve.
Each tool distributed has a varying number of users using the tools at the same time (anywhere between 10 - 300+). I understand that Access is not the ideal solution giving the potential number of concurrent users but once again, this is out of my control.
When the tools are in use, users sometimes receive the The database has been placed in a state by user 'Admin' on machine '***' that prevents it from being opened or locked. error.
Given the volume of transactions, the error occurs roughly 0.001% of the time.
I've read a number of articles on the topic, the majority of which end with an assumption that an object within the database is being modified or that a user is entering Design Mode which should be addressed by having a separated front and back end.
Question
A number of different types of queries occur including SELECT and INSERT INTO statements. The users do not directly access the database file and therefore no object as being modified and nothing is being put into Design Mode therefore why are users experiencing this error? Is it due to the shear number of users?
I use the same approach towards connecting to the database using the below method:
Public Function fGetOrderStatus() As Variant()
Dim oDB As ADODB.Connection
Dim oCM As ADODB.Command
Dim oRS As ADODB.Recordset
On Error GoTo Err:
Set oDB = New ADODB.Connection
oDB.Open gcConn
Set oCM = New ADODB.Command
With oCM
.ActiveConnection = oDB
.CommandText = "SELECT OrderStatusId, OrderStatus FROM ct_elh_OrderStatus WHERE Deleted Is Null"
.CommandType = adCmdText
Set oRS = .Execute
End With
If Not oRS.BOF And Not oRS.EOF Then
fGetOrderStatus = oRS.GetRows()
Else
Erase fGetOrderStatus
End If
oRS.Close
Set oRS = Nothing
oDB.Close
Set oDB = Nothing
Exit Function
Err:
MsgBox ("An unexpected error occurred. Please try again later."), vbCritical, "Error"
End Function
The following seems to be the cause of this error:
Jet locks a block of 256 bits in the MDB header. Included in this block of bits are bits that indicate a "passive shutdown", which causes the error message described in the "Symptoms" section to occur. You may have users that open and close the MDB file rapidly and the bits are not getting unlocked quickly enough. When a user tries to open the MDB file, if the program is not able to read the bits, Jet assumes that the user is in a "passive shutdown" or "admin mode", and therefore will not let the user open the MDB file.
It seems this error is due to the fact that you open and close your database to rapidly, especially if there are multiple users involved.
Why not open the connection when opening the user's front-end, and closing the connection when the user wishes to quit?
More information to be found here.
I'm following code I found on another site. Here's the basics of my code:
Dim SQL As String
Dim connString As String
connString = "ODBC;DSN=DB01;UID=;PWD=;Database=MyDatabase"
SQL = "Select * from SomeTable"
With Worksheets("Received").QueryTables.Add(Connection:=connString, Destination:=Worksheets("Received").Range("A5"), SQL:=SQL)
.Refresh
End With
End Sub
The problem with doing this is every single time they hit the button assigned to this it creates a new connection and doesn't ever seem to drop it. I open the spreadsheet after testing and there are many versions of the connection listed under Connections.
Connection
Connection1
Connection2
I can't seem to find a way to close or delete the connections either. If I add ".delete" after ".Refresh" I get a 1004 error. This operation cannot be done because the data is refreshing in the background.
Any ideas how to close or delete the connection?
You might ask yourself why you're creating a QueryTable every time in your code. There are reasons to do it, but it usually isn't necessary.
QueryTables are more typically design-time objects. That is, you create your QueryTable once (through code or the UI) and the you Refresh the QueryTable to get updated data.
If you need to change the underlying SQL statement, you have some options. You could set up Parameters that prompt for a value or get it from a cell. Another option for changing the SQL is changing it in code for the existing QueryTable.
Sheet1.QueryTables(1).CommandText = "Select * FROM ...."
Sheet1.QueryTables(1).Refresh
You can select different columns or even different tables by changing CommandText. If it's a different database, you'll need a new connection, but that's pretty rare.
I know that doesn't answer your question directly, but I think determining whether you really need to add the QueryTable each time is the first step.
For more on Parameters, see http://dailydoseofexcel.com/archives/2004/12/13/parameters-in-excel-external-data-queries/ It's for 2003, so there are few inconsistencies with later versions. The basics are the same, you just may need to learn about the ListObject object if you're using 2007 or later.
I had the same issue. The previous answer while a definite step in the right direction is a PITA.
It did however allow me to refine my search and the winner is...
http://msdn.microsoft.com/en-us/library/bb213491(v=office.12).aspx
i.e. for your existing QueryTable Object just do this:
.MaintainConnection = False
Works ever so swell. No more Access DB lock file after the data is refreshed.
You should declare the connection as a separate object then you can close it once the database query is complete.
I don't have the VBA IDE in front of me, so excuse me if there are any inaccuracies, but it should point you in the right direction.
E.g.
Dim SQL As String
Dim con As connection
Set con = New connection
con.ConnectionString = "ODBC;DSN=DB01;UID=;PWD=;Database=MyDatabase"
Worksheets("Received").QueryTables.Add(Connection:=con, Destination:=Worksheets("Received").Range("A5"), SQL:=SQL).Refresh
con.close
set con = nothing
I've found that by default new connections created this way are called "Connection". What I am using is this snippet of code to remove the connection but retain the listobject.
Application.DisplayAlerts = False
ActiveWorkbook.Connections("Connection").Delete
Application.DisplayAlerts = True
It can easily be modified to remove the latest added connection (or if you keep track of the connections by their index).
Application.DisplayAlerts = False
ActiveWorkbook.Connections(ActiveWorkbook.Connections.Count).Delete
Application.DisplayAlerts = True
Instead of adding another query table with the add method, you can simply update the CommandText Property of the connection. However you have to be aware that there is a bug when updating the CommandText property of an ODBC connection. If you temporarily switch to an OLEDB connection, update your CommandText property and then switch back to ODBC it does not create the new connection. Don't ask me why... this just works for me.
Create a new module and insert the following code:
Option Explicit
Sub UpdateWorkbookConnection(WorkbookConnectionObject As WorkbookConnection, Optional ByVal CommandText As String = "", Optional ByVal ConnectionString As String = "")
With WorkbookConnectionObject
If .Type = xlConnectionTypeODBC Then
If CommandText = "" Then CommandText = .ODBCConnection.CommandText
If ConnectionString = "" Then ConnectionString = .ODBCConnection.Connection
.ODBCConnection.Connection = Replace(.ODBCConnection.Connection, "ODBC;", "OLEDB;", 1, 1, vbTextCompare)
ElseIf .Type = xlConnectionTypeOLEDB Then
If CommandText = "" Then CommandText = .OLEDBConnection.CommandText
If ConnectionString = "" Then ConnectionString = .OLEDBConnection.Connection
Else
MsgBox "Invalid connection object sent to UpdateWorkbookConnection function!", vbCritical, "Update Error"
Exit Sub
End If
If StrComp(.OLEDBConnection.CommandText, CommandText, vbTextCompare) <> 0 Then
.OLEDBConnection.CommandText = CommandText
End If
If StrComp(.OLEDBConnection.Connection, ConnectionString, vbTextCompare) <> 0 Then
.OLEDBConnection.Connection = ConnectionString
End If
.Refresh
End With
End Sub
This UpdateWorkbookConnection subroutine only works on updating OLEDB or ODBC connections. The connection does not necessarily have to be linked to a pivot table. It also fixes another problem and allows you to update the connection even if there are multiple pivot tables based on the same connection.
To initiate the update just call the function with the connection object and command text parameters like this:
UpdateWorkbookConnection ActiveWorkbook.Connections("Connection"), "exec sp_MyAwesomeProcedure"
You can optionally update the connection string as well.
If you want to delete if right after refresh you should do the refresh not in the background (using first parameter -> Refresh False) so that you have proper sequence of actions
Try setting the QueryTable.MaintainConnection property to False...
"Set MaintainConnection to True if the connection to the specified data source is to be maintained after the refresh and until the workbook is closed. The default value is True! And there doesn't seem to be a UI check box for this (Read/write Boolean)"
Still relevant years later...battling the same issue and this is the most helpful thread out there. My situation is a variant of the above and I will add my solution when I find it.
I am using an Access database for my data source and establish a querytable on a new sheet. I then add two more new sheets and try to establish a querytable using the same connection on each of them, but to a different Access table. The first querytable works just fine and I use .QueryTables(1).Delete and setting the querytable object to Nothing to make it disconnected.
However, the next sheet fails on establishing a new querytable using the same connection, which was not closed. I suspect (and will add the solution below) that I need to drop the connection before deleting the querytable. Rasmus' code above looks like the likely solution.