MS Access Admin State During Statement Execution - sql

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.

Related

calling a uid and pwd file into an access db for a system dsn

I have 5 different MS Access 2013 db files which connect to system dsn files(Linked Server)
The machine/system dsn's are Oracle based but the issue I am running into is 1 my password isn't encryped and has to be changed every 90 days and
2 I have to open each file and save my password multiple times when its changed.
I want to have a secure location which has the credentials stored and pass this into access so that they are not visible to other users and
not having to save my password on each access file and relink it.
I have googled for over 2 days but can't find anything other than looking at connection strings which still doesn't solve the problem. What do I need to look at to solve this?
Sorry I dont have code as I just use the linked tables wizard inside of ms access.
#Albert D I couldn't get your code to work but I have done the following which fixes the issue on the tables but not the Pass Through queries
Created a File DSN and linked some tables to the access database.
Created an Excel File to store my UserName And Passwords but my credentials in the pass-through query still show which I am stuck on?
Option Compare Database
Function Connections()
On Error Resume Next
'delete query if exists
DoCmd.DeleteObject acQuery, "PTQ"
Err.Clear
On Error GoTo 0 '"on error" statement here
'GET EXCEL LOGIN DETAILS
Set xlsApp = CreateObject("Excel.Application")
Dim WkBk As Excel.WorkBook
Set WkBk = xlsApp.WorkBooks.Open(FileName:="C:\folderlocation\filename.xlsx")
Dim USERLIST As String
Dim PWDLIST As String
USERLIST = WkBk.Sheets(1).Range("A2").Value
PWDLIST = WkBk.Sheets(1).Range("B2").Value
If Not (xlsApp Is Nothing) Then xlsApp.Quit
'end excel stuff
Dim db As DAO.Database
Dim qdExtData As QueryDef
Dim strSQL As String
Set db = CurrentDb
'passthrough query statement
strSQL = "SELECT * FROM table"
Set qdExtData = db.CreateQueryDef("PTQ")
ServerName = "Server1"
qdExtData.Connect = "ODBC;DRIVER={Oracle in OraClient11g_home1};Server=" & ServerName & ";DBQ=Server1;UID=" & USERLIST & ";Pwd=" & PWDLIST & ""
qdExtData.SQL = strSQL
qdExtData.Close
db.Close
Set db = Nothing
End Function
I then Set up a runcode macro called AutoExec
Ok, first up?
I would avoid (not use) a system, or a user DSN. The reasons are MANY, but these types of DSN's require not only a external reference, but also often require elevated registry rights. And worse yet, your password will be in plain view.
The best solution is to use what is called a DSN-less connection. And you can create these connections even without code!
And even better? If the server + database name is NOT changed, but ONLY the password? You can change the userID + password WITHOUT having to re-link the tables (ideal for you with a changing password, but NOT the server + database being changed).
Even better better? If you adopt a wee bit of "log on" code, then you don't have to store the password in the table links! What this means if someone decides to fire up access, and say import your linked tables? Well, for one, they will not be able to open the tables, and EVEN better yet the uid/password is NOT part of, nor is it stored in the connection string for each linked table. As noted, because of this, you can change the UID, and not have to re-link the tables.
The first step:
First up, ALWAYS (but ALWAYS!) when you link the tables, use a FILE dsn. this is imporant since when using a FILE dsn in access, they are automatic converted to DSN-less connections for you.
In other words, if you link your tables with a FILE dsn, then once the tables are re-linked, then you can even delete or toss out the DSN. And this means you can deploy the linked database (front end) to any workstation. You will not have to setup a DSN on that workstation, and in fact don't have to setup anything at all. (you will as before of course require the oracle database driver).
What the above means is when a password is updated/changed, you could then simply roll out a new front end. In fact, you likely have some auto updating ability for your application. (and if you don't', then you could (should) cobble together a bit of code to do this for you). So, you should have some means to roll out a new version of your software. After all, you REALLY have to install your "application" on each workstation LIKE you do with all other software, right???
So, there are two parts here:
Adopting a FILE dsn for the table links. As noted, once you do this, then you don't need the DSN anymore. This is great for distribution to each workstation.
Also MAKE SURE when you do link, you do NOT check the box to save the password. This is to ensure that uid/password is NOT saved in the connection strings.
So, if tables don't have uid/password, then how will they work?
Well, what you do is execute a "logon" to the database in your startup code. Once you execute this logon, then all linked tables will work! It is this "small" bit of code that can read the uid/password you place in an external file. Where you place this uid/password is up to you. It could be embedder in the code, or even some external text file that you read on startup. And it could even be a local table in the front end. (this idea would work well if you have some kind of automatic update system for when you roll out the next great version of your software.
So, with the ability to execute a logon, and not having to re-link the tables, then we have a EASY means to change the password.
So, you have to:
go dsn-less. Thankfully, access does this by default, but ONLY if you use a FILE dsn.
Get/grab the code to execute a logon to the database. In fact, you likly have to delete all your table links. Exit Access, then re-start Access. Now, run your logon code, and THEN re-link your tables (using a FILE dsn you make).
The code to execute a logon is this:
Function TestLogin(strCon As String) As Boolean
On Error GoTo TestError
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
Set dbs = CurrentDb()
Set qdf = dbs.CreateQueryDef("")
qdf.connect = strCon
qdf.ReturnsRecords = False
'Any VALID SQL statement that runs on server will work below.
qdf.sql = "SELECT 1 "
qdf.Execute
TestLogin = True
Exit Function
TestError:
TestLogin = False
Exit Function
End Function
The above of course requires a correctly formed conneciton string.
So, to make ALL of this work, you can adopt the FILE dsn, and use above. You will likly cobbile together some code to build you a connection string. Then add to your code some code to read an external ext file, or have the UID/password in some table. This logon code as per above has to run BEFORE any form or linked table is used or touched.
The whole process is simple, but only if you break down this into the correct steps.
How this logon trick, and example code works is not complex, but a full article explaining this approach is outlined here
Power Tip: Improve the security of database connections
https://www.microsoft.com/en-us/microsoft-365/blog/2011/04/08/power-tip-improve-the-security-of-database-connections/

Opening a DBF file of 262 columns, increased from 256 columns, returns "...file.dbf is not a table"

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.

VBA ADO: Could Not Use <Filename>; file already in use

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?

Record locking in vba

i have a VBA application which is spitted into two one for front end; and backend while updating how to prevent two users edit it ??
Access has locking. From Access Help:
Specify the locking level used in an Access database in a multiuser environment
On the Tools menu, click Options.
Click the Advanced tab.
To make record-level locking the new
default setting for the current
Microsoft Access database, select the
Open databases using record-level
locking check box.
To make page-level
locking the new default setting for
the current Access database, clear the
Open databases using record level
locking check box.
Notes
This setting takes place the next time
you open the Access database, but you
must use the Open command on the File
menu rather than the list of most
recently used files at the end of the
File menu. This behavior is the same
as the setting for the default open
mode.
If you select Open databases using
record level locking, this becomes the
default behavior for access to data
through a form, a datasheet, and code
that uses a recordset object to loop
through records, but not through
action queries, nor through code that
performs bulk operations using SQL
statements. For more information, see
Chapter 16, "Multiuser Database
Applications," in the Microsoft Office
2000/Visual Basic Programmer's Guide.
Yoy may try to invoke pessimistic locking
Dim cnn As ADODB.Connection
Dim rst As ADODB.Recordset
Set cnn = New ADODB.Connection
cnn.ConnectionString = " Provider=sqloledb;" & _
"Data Source=(local);Initial Catalog=pubs;uid=sa;pwd="
cnn.Open
Set rst = New ADODB.Recordset
rst.ActiveConnection = cnn
rst.CursorType = adOpenKeyset
rst.LockType = adLockPessimistic 'Invoke Pessimistic Locking
rst.CursorLocation = adUseServer
rst.Open "Select * from Table Where ID ='" _
& strID & "'", _
Options:=adCmdText
rst!Name = "New name" 'Lock occurs here
'... when it is locked, you may do other operations
rst.Update 'Lock Released Here
You will have to implement error handling, because when 2nd client wants to edit and cannot lock the record during timeout, error will be raised.
However pessimistic locking is not the best scenario, I would think about optimistic locking and either First Wins or Last Wins strategy
Here is an online book Alison Balter's mastering Microsoft Access 2000 development, it should help you.

Intermittent error when attempting to control another database

I have the following code:
Dim obj As New Access.Application
obj.OpenCurrentDatabase (CurrentProject.Path & "\Working.mdb")
obj.Run "Routine"
obj.CloseCurrentDatabase
Set obj = Nothing
The problem I'm experimenting is a pop-up that tells me Access can't set the focus on the other database. As you can see from the code, I want to run a Subroutine in another mdb. Any other way to achieve this will be appreciated.
I'm working with MS Access 2003.
This is an intermittent error. As this is production code that will be run only once a month, it's extremely difficult to reproduce, and I can't give you the exact text and number at this time. It is the second month this happened.
I suspect this may occur when someone is working with this or the other database.
The dataflow is to update all 'projects' once a month in one database and then make this information available in the other database.
Maybe, it's because of the first line in the 'Routines' code:
If vbNo = MsgBox("Do you want to update?", vbYesNo, "Update") Then
Exit Function
End If
I'll make another subroutine without the MsgBox.
I've been able to reproduce this behaviour. It happens when the focus has to shift to the called database, but the user sets the focus ([ALT]+[TAB]) on the first database. The 'solution' was to educate the user.
This is an intermittent error. As this is production code that will be run only once a month, it's extremely difficult to reproduce, and I can't give you the exact text and number at this time. It is the second month this happened.
I suspect this may occur when someone is working with this or the other database.
The dataflow is to update all 'projects' once a month in one database and then make this information available in the other database.
Maybe, it's because of the first line in the 'Routines' code:
If vbNo = MsgBox("Do you want to update?", vbYesNo, "Update") Then
Exit Function
End If
I'll make another subroutine without the MsgBox.
I've tried this in our development database and it works. This doesn't mean anything as the other code also workes fine in development.
I guess this error message is linked to the state of one of your databases. You are using here Jet connections and Access objects, and you might not be able, for multiple reasons (multi-user environment, unability to delete LDB Lock file, etc), to properly close your active database and open another one. So, according to me, the solution is to forget the Jet engine and to use another connexion to update the data in the "other" database.
When you say "The dataflow is to update all 'projects' once a month in one database and then make this information available in the other database", I assume that the role of your "Routine" is to update some data, either via SQL instructions or equivalent recordset updates.
Why don't you try to make the corresponding updates by opening a connexion to your other database and (1) send the corresponding SQL instructions or (2) opening recordset and making requested updates?
One idea would be for example:
Dim cn as ADODB.connexion,
qr as string,
rs as ADODB.recordset
'qr can be "Update Table_Blablabla Set ... Where ...
'rs can be "SELECT * From Table_Blablabla INNER JOIN Table_Blobloblo
set cn = New ADODB.connexion
cn.open
You can here send any SQL instruction (with command object and execute method)
or open and update any recordset linked to your other database, then
cn.close
This can also be done via an ODBC connexion (and DAO.recordsets), so you can choose your favorite objects.
If you would like another means of running the function, try the following:
Dim obj As New Access.Application
obj.OpenCurrentDatabase (CurrentProject.Path & "\Working.mdb")
obj.DoCmd.RunMacro "MyMacro"
obj.CloseCurrentDatabase
Set obj = Nothing
Where 'MyMacro' has an action of 'RunCode' with the Function name you would prefer to execute in Working.mdb
I've been able to reproduce the error in 'development'.
"This action cannot be completed because the other application is busy. Choose 'Switch To' to activate ...."
I really can't see the rest of the message, as it is blinking very fast. I guess this error is due to 'switching' between the two databases. I hope that, by educating the user, this will stop.
Philippe, your answer is, of course, correct. I'd have chosen that path if I hadn't developed the 'routine' beforehand.
"I've been able to reproduce this behaviour. It happens when the focus has to shift to the called database, but the user sets the focus ([ALT]+[TAB]) on the first database. The 'solution' was to educate the user." As it is impossible to prevent the user to switch application in Windows, I'd like to close the subject.