VBA to properly access multi user database - excel

I have an Excel Add-In project that I'm working on which has multiple users accessing a database on the server. Currently all the code works and everything processes correctly as long as only one user is accessing the database at a time. I'm using DAO to access the database and passing an SQL string it to retrieve records using the following lines of code
Set db = OpenDatabase(g400DBPath, , True)
Set rs = db.OpenRecordset(sSQL, dbOpenSnapshot)
This creates an issue where if more than one person attempts to access the database at the same time, only the first person is able to access it. I tried changing the recordset line of code to the following
Set rs = db.OpenRecordset(sSQL, dbOpenSnapshot, , dbOptimistic)
but that gives me the following error: Run-time error '3001: Invalid Argument
How would I go about setting the access to the record set so that multiple users can run the report? The users are not updating any information in the database at all, everything is read only.

Your problem is: a snapshot-type recordset is always read-only. Specifying any edit lock options will throw a run-time error, since snapshot-type recordsets don't do locking.
I can't reproduce the behavior, but you can try the following:
Set db = OpenDatabase(g400DBPath, False, False) 'Read-only can lead to exclusive locks since more specific locks are stored in the DB
Set rs = db.OpenRecordset(sSQL, dbOpenSnapshot, dbReadOnly)
Alternatively, you could try using ADO and disconnected recordsets. DAO doesn't offer that functionality as far as I know.

Related

Excel VBA Using MS Access DB Should I close connection?

I'm creating an Excel VBA program for people at my office. The way I intend it to work is each person will have their own "portal", an Excel document where they can interact with the DB with their own user settings. This way there won't be issues with multiple people trying to use an Excel file at the same time.
The thing I'm not sure about is the Access Database I'm setting up. It's a single DB file that everyone can access. Currently the plan is to create the connection this way:
Dim accessFileLoc As String: accessFileLoc = "C:\Users\gmloo\OneDrive\Desktop\Grab Project\GrabDB.accdb"
'create the connection object, and open the connection
Set accessCon = CreateObject("ADODB.connection")
'i HOPE setting the mode like this will allow multiple users to connect to the same
'db at the same time
accessCon.Mode = 16 + 3 'adModeShareDenyNone + adModeShareReadWrite
accessCon.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & accessFileLoc
accessCon is a global Excel VBA variable that is currently designed to hold the connection as long as the Excel program is open. My question is, will this cause problems with other users all Querying the DB file at the same time? Should I be closing the connection for each user while it isnt needed, then re-establish it when they have to do a query? There's probably going to be up to 15 people using their Excel portal at any given time.
Thanks for your help.
You should be fine with multiple users connecting at the same time. However, due to memory and performance issues, I would recommend you close the connection after each query.

Bypass transaction for logging

I'm importing data from Excel to Access. During the import I validate it and I insert only rows that are ok. Errors are logged into another table that is later used by a report. The import is done manually with a VBA macro. I use CurentDb.OpenRecordset for writing.
Current solution: only valid data + keep logs
I import data from Excel row by row and keep them in the database after the import. Even if I find errors in other rows.
At the same time I log errors into another table.
Desired solution: all or nothing + keep logs
Commit the imported data only if all data is valid.
Keep the error log also when the imported data is rolled back to show a report.
Issue
When I rollback the imported data, the logs are also rolledback so I cannot use it for reporting.
Question
Is there a way to bypass the import transaction so that the log is always written?
You can create multiple workspaces and corresponding database objects, which have separate transactions, even on the database that's currently open. Note that this won't work if you have an exclusive lock on the database.
Dim wsLog As DAO.Workspace
Dim wsImport As DAO.Workspace
Dim dbImport As DAO.Database
Dim dbLog As DAO.Database
Dim dbe As DAO.DBEngine
Set dbe = DBEngine
Set wsLog = dbe.CreateWorkspace("wsLog", "Admin", "")
Set wsImport = dbe.CreateWorkspace("wsImport", "Admin", "")
Set dbLog = wsLog.OpenDatabase(CurrentProject.FullName)
Set dbImport = wsImport.OpenDatabase(CurrentProject.FullName)
wsImport.BeginTrans
dbImport.Execute "INSERT INTO Table1(Field1) VALUES(1)" 'Do your imports/processing in this workspace
wsLog.BeginTrans
dbLog.Execute "INSERT INTO Table2(Field1) VALUES(1)" 'And logging here
wsLog.CommitTrans 'Commit log
wsImport.Rollback 'Rollback import afterwards
'Result: Table2 is changed, Table1 isn't
Note that while it's not required to use a separate logging database, if you use Access for storage, I do recommend it.
And, as said in the comments, you can just work with a temporary table too.
You create a permanently invisible table that's intended for temporary use using CreateTableDef with the dbHiddenObject constant:
CreateTableDef("TableName", dbHiddenObject)
It won't get removed automatically, so it's not temporary in that sense, though. And keep in mind that deleting + recreating temporary tables will increase the database size, likely requiring more compact operations.

User Defined Functions (UDF) from Access Query to Excel using VBA OpenRecordset failed - Undefined Function

How do I get the results of a query from Access into Excel if it has a UDF?
I receive the following error: "Run-time error '3085': Undefined function 'XXXX' in expression". The error occurs when opening an (access query) recordset from Excel VBA. The query being opened has a user defined function (UDF) which is triggering the error.
The code is in Excel Office 365. The query is in Access Office 365.
I have successfully utilized the query being called (and others with the UDFs) for about twelve months, and "suddenly" it is not working any more. I have googled and tested many options with no success.
Most threads say it can't be done, or to not use a udf but try a built-in that works. I am challenging those responses because it has worked previously. The main udf I am using is one called "iMax" which is written about in other posts. It functions like max() in Excel. (No max(x,y) function in Access)
I have also seen threads that suggest executing this in two steps: 1 - change the query to a make table query. 2 - pull the table results into Excel. While I could maybe get away with this (after much rework), it would result in me making many temporary tables with thousands and thousands of rows and doesn't seem very slick.
I have compiled vba and compacted the db with no impact to my problem.
As a long shot I created a dummy database with a simple udf public function that returned the number 1, a simple query that returns three records and a field for the function results. This gets the same error when pulling into Excel.
Sub RunQuery()
Dim MyDatabase As dao.Database
Dim qdf As dao.QueryDef
Dim rs As dao.Recordset
Dim qryname As object
Dim SheetName As String
Set MyDatabase = DBEngine.OpenDatabase _
("SomePath\SomeFilename.accdb")
For Each qryname In Range("SomeRange")
Set rs = MyDatabase.OpenRecordset(qryname) '<<<ERROR IS HERE
SheetName = "SomeSheetName"
With Sheets(SheetName)
.ListObjects(SomeTableName).DataBodyRange.Rows.ClearContents
.Range("A2").CopyFromRecordset rs
End With
Set rs = Nothing
Set qdf = Nothing
Next qryname
End Sub
For all queries in the For loop that do not have a udf, the results are pulled and dumped into a series of tables in Excel. Any query with a udf errors at the "Set rs = Mydatabase.OpenRecordset(qryname)
If you run the query within an Access application session, as Gustav suggested, the expression service can handle the UDF in your query.
Here is a quick tested Excel VBA snippet which pulls data from a query which includes a UDF:
Const cstrDbFile As String = "C:\share\Access\Database2.accdb"
Dim objAccess As Object
Dim rs As Object
Dim ws As Worksheet
Dim strSelect As String
Set objAccess = CreateObject("Access.Application")
objAccess.Visible = True ' useful during testing '
objAccess.OpenCurrentDatabase "C:\share\Access\Database2.accdb"
strSelect = "SELECT ID, DummyFunction('a', '', 'c') FROM Dual;"
Set rs = objAccess.CurrentDb.OpenRecordset(strSelect)
If Not rs.EOF Then
Set ws = ThisWorkbook.Sheets("Sheet1")
ws.Range("A1").CopyFromRecordset rs
End If
rs.Close
objAccess.Quit
Most threads say it can't be done,
and they are right.
Your only option is to use automation to open an instance of Access and, within this, run the query.
Well, as noted, most are saying this should not work.
However, if you are 100% sure it was and did work at one time?
You need to set the "sandbox" mode of the JET (now ACE) database engine.
The expression service normally does not allow evaluation of VBA functions as a security setting to prevent SQL injection, or code running outside of Access to allow SQL to run + call VBA functions. At one time, this feature did default to "on", but now the default is set to access only.
You have to set the folder where Access application as trusted. This should allow the VBA functions to now work. so, make sure you set the folder as trusted.
If the location (folder) where your access application is NOT trusted, then Access will use sandbox mode, and VBA in the SQL will not run.
If the location is trusted, THEN access uses the registry setting on your computer.
My bets are that the location is not trusted - so you always get sandbox mode for SQL in Access.
If you are 100% sure that the folder location is set as trusted in Access, and you still receive the errors, then you have to change the registry setting for Access "sandbox" mode.
The setting in the registry is outlined here:
https://support.office.com/en-us/article/Turn-sandbox-mode-on-or-off-to-disable-macros-8CC7BAD8-38C2-4A7A-A604-43E9A7BBC4FB
The registry settings are:
for x32 bit access:
Software\Microsoft\Office\ClickToRun\Registry\Machine\Software\
Wow6432Node\Microsoft\Office\16.0\Access Connectivity Engine\Engines
The above is for Office 2016
14 = 2010
15 = 2013
16 = 2016
The key value for sandbox mode is:
0 to 3
0 Sandbox mode is disabled at all times.
1 Sandbox mode is used for Access, but not for non-Access programs.
2 Sandbox mode is used for non-Access programs, but not for Access.
3 Sandbox mode is used at all times. This is the default value, set when you install Access
So, from above, you want a setting of 0.

Selecting a schema to connect to when connecting over an ODBC connection in Excel's Power Query

So I have a fairly simple query (proof of concept) where I'm able to dynamically pull in data from a database depending on my Excel sheet inputs. For example:
let
Source = Odbc.Query("dsn=Dev 2", "select * #(lf)from job_id JI#(lf)where JI.job_ID = " & Text.From(GetNamedRange("SQLJobInstanceID")) & "#(lf)")
in
Source
Hurray, it works! However, when I was setting up the connection, I needed to log in. Part of the login includes the schema that I wanted to connect to. Username[Schema] is how it's done. Now that I've connected, I'd like to select a different schema to connect to - but I'm not sure how I can either modify the schema, or log out and log back in.
(Also curious if the connection is storing my credentials, which would be problematic, or if other people would need to log in with their credentials)
I believe I can also edit which ODBC connection I'm using by editing the "dsn=Dev 2" portion of the code (Again, will probably turn it into a named range drop down menu.... the challenge becomes dynamically figuring out what ODBC options each user has and pulling those through...)
Thank you

Excel VBA Connection to MS Access (Read/Write) to Multi User Tables/ Queries

I have got to the end of trying to figure out why I can't get correct settings for multi user read and update to MS Access tables from Excel (Office 2013). So I am hoping someone here can help.
Background - I have an MS Access database that contains all data in tables that is used by an Excel application that runs on each users machine. The .accdb file resides on a shared QNAP NAS. I have designed the system so there is a user list table that is accessed on startup via a form, allowing users to 'login' to the system. When a user is logged in, the intention is to record a Yes/No field in this table to prevent the same user name being used on two different machines accessing the same subset of data.
Now this all works well from one machine or multiple excel instances on one machine.
When I load the excel application on multiple machines, the Yes/No field is inconsistent between the machines, unless I stop the Excel application on the machine and restart. Sometimes I get access errors or database corruption errors in this multi user setup - so I am clearly doing something wrong - but can't figure it out. Help Please!!
The connection from the excel application is done as follows:
' connect to the Access database
Set cn = New ADODB.Connection
With cn
.Provider = "Microsoft.ACE.OLEDB.12.0"
.Mode = adModeShareDenyNone
.CursorLocation = adUseServer
.Open ThisWorkbook.Worksheets("Title").Range("B1") & "cms.accdb"
End With
The recordset is opened in excel as follows (which does not show a user set as logged on another machine in via the yes/no field unless I start the application AFTER they have 'logged in')
Set rs = New ADODB.Recordset
sSQL = "SELECT [access list].* FROM [access list] where [access list].[user] = '" & Me.userlist.Text & "';"
rs.Open sSQL, cn, adOpenDynamic, adLockPessimistic
Application.CalculateUntilAsyncQueriesDone ' not sure I need this????
The test for this user being logged in is:
If rs.Fields("Logged in").Value = -1 Then
fname = rs.Fields("user").Value
rs.Close
cn.Close
Set rs = Nothing
Set cn = Nothing
MsgBox fname & " is already logged in - please select another user or exit"
Else
userid = rs.Fields("user").Value
rs.Close
Set rs = Nothing
sSQL = "Update [access list] Set [access list].[logged in] = '-1' WHERE [access list].[user] = '" & userid & "'"
cn.Execute (sSQL)
MsgBox "Welcome " & userid & "! Starting system for your contracts."
cn.Close
Set cn = Nothing
Unload Me
End If
So what am I doing wrong? I've tried all different ways with little difference. (Note - I am not trying to test race conditions by both logging in to the same user at exactly the same time - a situation that is not of concern - this is just a simple lock out model I'm implementing). I thought what I was doing was keeping record level locking, made sure I could see any other user change to the record in each application on each machine. I'm stumped! Opening MS Access and looking at the database shows the inconsistency - on one machine the user is logged in, on the other the user is not. I'm thinking its how I connect to the access database or some setting in the access database that I haven't got right - but thats just a thought.
One last twist to this - my test environment machines are Apple MacBooks running Win 8.1/ Office 2013 in a VMware Fusion virtual machine. I do not have the environment to test on native Win 8.1 machines at the moment, but this will be the eventual target. Maybe it will work on the target environment, but can't test that yet and as that is the last step, working out any problems with the code in the test environment is my current priority.
Russ
I see this post is old, so you may have moved on already, but thought I would answer in case it helps someone. I have been investigating a similar issue where cursor location and lock type seem to be specified correctly, but I still sometimes get inconsistent read results. I am no expert, but this may have to do with the cache refresh and write delays associated with JET/ACE/Access databases. You may want to play around with the Page Timeout and Flush Transaction Timeout settings and consider using the DBEngine.Idle dbRefreshCache statement before doing a read - although this is a DAO feature.

Resources