Set commandtimeout for linked SQL Tables/Views in Access front end - sql-server

We have moved some back-end data tables over from a network drive (mbd file) to being on an SQL Server database. Things mostly work great, but if staff are accessing things through a VPN (which slows things down a lot), then we get connection errors when we run reports that retrieve a lot of data. My guess is that I need to set a timeout to a larger value, and I did some research and it seems that I need to set the commandtimeout (or maybe query timeout?).
Below is the VBA code we use to connect the SQL Server tables/views to our Access front end from the SQL Server back end. Am I right that I likely need to specify a commandtimeout? Where in this would we add the commandtimeout (or other timeout) value?
Public Sub CreateSQLLinkedTable(strSourceTableName As String, strNewTableName As String)
'************************************************************************************
'* Create a linked table in the current database from a table in a different SQL Server file.
'* In: *
'* strNewTableName - name of linked table to create *
'* strSourceTableName - name of table in database *
'************************************************************************************
Setup:
Dim tdf As TableDef
Dim strConnect As String, strMsg As String
Dim myDB As Database
' set database vars
Set myDB = CurrentDb
Set tdf = myDB.CreateTableDef(strNewTableName)
MakeConnections:
On Error GoTo OnError
' turn system warnings off
DoCmd.SetWarnings False
' define connect string and source table
' We do not need to specify the username (Uid) and password (Pwd) in this connection
' string, because that information is already cached from the connection to the SQL
' Projects database that we created in CheckSQLConnection() that was run to check connection
' to the database. So here we can have a connection string without the Uid and Pwd.
With tdf
.Connect = "ODBC;Driver={SQL Server};" & _
"server=" & myServer & ";" & _
"database=" & mySQLDB & ";"
.SourceTableName = strSourceTableName
End With
' execute appending the table
myDB.TableDefs.Append tdf
' turn system warnings back on
DoCmd.SetWarnings True
ExitProgram:
' this block of code will run if there are no errors
Exit Sub
OnError:
' this block of code runs if there is an error, per On Error assignment above
' display error message with details
MsgBox "There was an error connecting to the SQL Server data source Projects. Error = " & err & ", Description: " & err.Description
'exit Projects
Call CloseFormsAndQuit
End Sub

There is an ODBC timeout property. Open the query in design view, and go to properties to see it. There is also an (ODBC) query timeout on the current database properties page. You can set it programmatically as well:
Dim objDB As DAO.Database
Set objDB = CurrentDb()
objDB.QueryTimeout = 120
http://www.geeksengine.com/article/how-to-change-timeout-value-for-access-sql.html
Also check the server configuration. There is a query timeout on server side.

Related

How to connect/link SQL db to Access database using SSIS?

I have a SQL database that centralizes all data within our agency; however, we also receive data from outside sources that needs to be parsed into our db structure. One of our partners maintains an Access 2016 database that they provide to us weekly to perform various analyses. My goal is to build an SSIS (2017) package that will move data from the Access db to SQL tables; replacing the Access db w/ an updated version and re-running the SSIS package on a scheduled basis.
I am trying to set up a connection manager for the Access database, but I cannot find a driver option in the provided DSN list for .accdb files, only .mdb files types, when I try to build the connection string. How do I create the connection for an Access 2016 database (i.e., .accdb)???
You don't need SSIS for something like this. You can simply export data from MS Access to SQL Server, without replying on another app.
This VBA sample will export data from a table in Access to SQL Server.
Option Compare Database
Private Sub Command0_Click()
Dim sTblNm As String
Dim sTypExprt As String
Dim sCnxnStr As String, vStTime As Variant
Dim db As Database, tbldef As DAO.TableDef
On Error GoTo ExportTbls_Error
sTypExprt = "ODBC Database" 'Export Type
sCnxnStr = "ODBC;DRIVER=SQL Server;SERVER=your_sever_name" & stServer & ";DATABASE=your_database_name" & stDatabase & ";Trusted_Connection=Yes"
vStTime = Timer
Application.Echo False, "Visual Basic code is executing."
Set db = CurrentDb()
'need a reference to Microsoft DAO 3.x library
For Each tbldef In db.TableDefs
'Don’t export System and temporary tables
Debug.Print tbldef.Name
If tbldef.Name Like "*Table1*" Then
sTblNm = tbldef.Name
DoCmd.TransferDatabase acExport, sTypExprt, sCnxnStr, acTable, sTblNm, sTblNm
End If
Next tbldef
MsgBox "Done! Time taken=" & (Timer - vStTime)
On Error GoTo 0
SmoothExit_ExportTbls:
Set db = Nothing
Application.Echo True
Exit Sub
ExportTbls_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure ExportTblsODST"
Resume SmoothExit_ExportTbls
End Sub
See the link below for more info.
https://www.mssqltips.com/sqlservertip/2484/import-data-from-microsoft-access-to-sql-server/

Executing stored procedure in MS Access through ADO connection - removing application role

I have an app developed in MS Access which uses ADO connections to SQL server (Microsoft SQL Server 2017) to execute numerous stored procedures. The ADO connections are all done through an application role in order to limit permissions.
In the latest update, I created a few new stored procedures which return several recordsets, which are pasted in Excel. My issue is that when I execute these stored procedures, the application role is removed... The query runs without any issues, but when it finishes running, the application role is unset.
The following is an example of one of the stored procs being called in VBA Access:
Public Function CDTExceptionsReport() As ADODB.Recordset
On Error GoTo ErrorHandler
Set objConn = DB.MaintainConnection
On Error GoTo 0
If objCmd_ER Is Nothing Then
Set objCmd_ER = New ADODB.Command
With objCmd_ER
.CommandType = adCmdText
.CommandTimeout = 60 ' increase command
.CommandText = "EXEC tool.ExceptionsReport;"
.Prepared = True
' set connection object
.ActiveConnection = objConn
End With
End If
Set CDTExceptionsReport = objCmd_ER.Execute
On Error GoTo 0
Exit Function
ErrorHandler:
MsgBox "Error: " & Err.DESCRIPTION & vbNewLine & "Number: " & Err.Number
On Error GoTo 0 ' reset error handling
End Function
Note that objConn is my connection object, and objCmd_ER is my global command object.
Through the immediate terminal in VBA, I can check what role is being activated by using the following in debug mode:
Set RS = objConn.Execute("SELECT CURRENT_USER")
?RS.Fields(0)
If I run this before the objCmd_ER.Execute line, I can see the application role is still in use. However, when I run this immediately after that statement, the application role is removed and my windows username is returned. Has anyone experienced this before?
I've executed this stored procedure in SQL Server directly, and it works fine and does not log the application role out. Therefore my thinking is it's something to do with the ADO connection.
Please let me know what further details would be helpful to provide. The stored procedure does not contain any DDL or DML language - just 4 select queries.
Thanks
Never do this!
.ActiveConnection = objConn
That casts the connection object objConn to a connection string, then creates a new connection using that connection string, and uses that as the active connection.
Instead, always do this:
Set .ActiveConnection = objConn
That actually sets the active connection to your connection object.

Create database in SQL Server (only once) using VBA Excel

I would like to create a database in SQL server using VBA (Excel) just the first time that I will run the code. So the second time I run the code, the database will exist, and it will not create another one.
#abarisone
`Public Sub CreateDBTable()
Dim dbConnectStr As String
Dim Catalog As Object
Dim cnt As ADODB.Connection
Dim dbName As String
Dim tblName As String, ServerName As String, UserID As String, pw As String
tblName = shControl.Range("B5") 'Table Name
ServerName = "SERVICESWS15" 'Enter Server Name or IP
dbName = shControl.Range("B4") 'Enter Database Name
UserID = "" 'Leave blank for Windows Authentification
pw = "" 'Leave blank for Windows Authentification
dbConnectStr = "Provider=SQLOLEDB;Data Source=" & ServerName & ";Initial Catalog=" & dbName & ";User Id=" & UserID & ";Password=" & pw & ";"
Set Catalog = CreateObject("ADOX.Catalog")
Catalog.Create dbConnectStr
Set Catalog = Nothing
Set cnt = New ADODB.Connection
With cnt
.Open dbConnectStr
.Execute "CREATE TABLE " & tblName & " (KEY nvarchar(100) NOT NULL, " & _
"Date nvarchar(100) NOT NULL, " & _
"PRIMARY KEY (KEY));"
End With
Set cnt = Nothing
End Sub
`
There is an error in this line:
Catalog.Create dbConnectStr
Error: No such interface supported
It's not very complicated. First make sure that you are referring to the appropriate ADO library like here on the screenshot.
Then your have certain building blocks you will have to use: first make a Connection object (with a connection string), then make a Command object, and last but not least use the ExecuteNonQuery method of Command on your connection object. It does what the name says: executes an SQL command without having a RecordSet as a return object. See examples in the documentation starting from here.
I have not tried it before, but for this to happen without error, you will probably have to set your connection string to the system database called "Master" if working on MS SQL Server.
Of course, the SQL commands you will have to execute are (1) check if the database exists, (2) create db and tables if not.
Then you can create your "normal" Connection object to your database.
WARNING: to be able to create a database, your technical user defined in the VBA script must have high (system admin) privileges which is definitely a HUGE RISK even if you protect your excel. If it's not a sandbox environment, DO NOT DO IT!

Keeping UID and PWD out of an ADO connection string in an ODBC DSN-less Database and a DAO cached connection?

I have used Ben Clothier's suggestion from his Office Blog Power Tip (http://blogs.office.com/2011/04/08/power-tip-improve-the-security-of-database-connections/) to create a DSN-less connection with cached credentials so that users' UID and PWD aren't saved, or required multiple times, when working in the Access interface. Have others done this? If so, what has been your approach when you need to use an ADO connection instead of DOA to reach SQL from Access via VBA? How do you open a adodb connection without having to provide the User ID and Password again, or having to put it in the code?
(I'm using Access 2013 frontend, SQL 2008 R2 backend, SQL Server Security)
Thanks in advance!
My Cached Connection code works like this:
Public Function InitConnect(strUserName As String, strPassword As String) As Boolean
' Description: Is called in the application’s startup
' to ensure that Access has a cached connection
' for all other ODBC objects’ use.
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
Dim rst As DAO.Recordset
Dim strConnection As String
strConnection = "ODBC;DRIVER=sql server;" & _
"SERVER=******;" & _
"APP=Microsoft Office 2010;" & _
"DATABASE=******;" & _
"Network=DBMSSOCN;"
Set dbs = DBEngine(0)(0)
Set qdf = dbs.CreateQueryDef("")
With qdf
.Connect = strConnection & _
"UID=" & strUserName & ";" & _
"PWD=" & strPassword & ";"
.SQL = "Select Current_User;"
Set rst = qdf.OpenRecordset(dbOpenSnapshot, dbSQLPassThrough)
End With
InitConnect = True
ExitProcedure:
On Error Resume Next
Set rst = Nothing
Set qdf = Nothing
Set dbs = Nothing
Exit Function
End Function
Then when I need to access data I can do this (Note the UID and PWD are not required):
Dim dbs As DAO.Database
Set dbs = OpenDatabase("", False, False, "ODBC;DRIVER=sql server;SERVER=*****;APP=Microsoft Office 2010;DATABASE=*****;Network=DBMSSOCN")
I can also set the ODBC connection to pass-through queries as well in Access or VBA. But these connections work only when the connection string is IDENTICAL to what was originally used in my Cached Connection code. So, when I need an ADODB connection (as it seems sometimes ADO is needed?), the string obviously isn't going to be identical.
For Example:
Dim cn As New ADODB.Connection
cn.Open "Provider = sqloledb;Data Source=*same as "SERVER"*;Initial Catalog=*same as "DATABASE"*;User Id=****;Password=****"
This type of connection only works if I supply a User Id and Password. How can I write it so that I don't need them? ~Thanks!
Although ACCESS has some weak points regarding security, you can do few things to minimize the risks. One of them would be compile the DB to ACCDE. This way VBA is compiled and not visible.
You can create a public function that returns a string
Public Function GET_CONNECTION_STRING() as STRING
' construct your connection string here with server name and password
GET_CONNECTION_STRING = "DRIVER={" & Driver & "};PORT=" & mPort & ";DATABASE=" & mDatabase & ";SERVER={" & mServer & "};UID=" & mUser & ";PWD={" & mPassword & "};"
End Function
then create an AutoExe macro that runs when the application is opened.
in your AutoExe perform refreshing links to your linked tables. something similar to what you have.
For Each tdf In db.TableDefs
If tdf.connect <> vbNullString Then
tdf.connect = GET_CONNECTION_STRING & ";TABLE=" & tdf.name
tdf.RefreshLink
End If
Next tdf
you can do the same for existing pass through queries:
For Each myQuerydef In MyDB.QueryDefs
If Left(myQuerydef.connect, 4) = "ODBC" Then
myQuerydef.connect = "ODBC;" & GET_CONNECTION_STRING
myQuerydef.Close
End If
Next
in addition you can have some other public functions to get current logged in username.
something like
public function getCrruserID() as int
'check your public variable crr_user_id if its empty redirect to login
if nz(crr_user_id,0) = 0 then
'go to login and save the user id after successful login
else
getCrruserID = crr_user_id
end if
end function
use simple DAO to execute sql code like
dim db as DAO.Database
set db = currentdb
dim rs as Dao.Recordset
set rs = db.openrecordset("select something from your linked table")
or
db.execute "update command", dbfailonerror
to your last question. if you save something in memory it will be destroyed once your application is closed.
EDIT:
if you have more than 50 linked tables it might be not a good idea to refresh them at every startup. Instead you can create a Local table containing your [local_Appversion, isFreshInstall] and some other variables as per your need. Every time your user receives an update the freshInstall will be true and code your App to connect and refresh all tables. (just to make sure client will get uninterrupted connection)
so in your autoExe code: if its freshInstall then
connect and refreshlinks if not just set the connectionString. (usually a splash screen after login to perform this action)
After successful connection just update the local isFreshInstall value to false for a quicker start next time.
you can also have a dedicated menu where user can click and refresh links manually.(in case if the connection get dropped)
something like
if your organisation has a domain you can allow trusted connection using windows login name
good luck.

How can I programmatically create a DSN?

I have an Access Form that uses a linked sql server table as a datasource. I will need to distribute this file to other users soon and I need a way to programmaticly install the DSN to their machines.
This is the process of manually setting up the link:
External Data > More > ODBC Database > Link to data source > Machine data source tab >
press new > user data source > sql server > name=up to you; server= serverName > How should SQL server verify the autheticity of the login ID? With windows NT authentication using the network login ID > Attach database File Name (database name) > choose the table and press ok
That is what I did to access my table but I would like it so that the user can press a button and get access to the table and at the same time be authenticated by using windows NT authentication.
I am having trouble finding a way to write this in access vba code can someone direct me in the right direction?
As a general rule you find MUCH better success by using a DSN less connection. This will eliminate many issues and problems. How to use a DSN less connection is outlined here:
http://www.accessmvp.com/DJSteele/DSNLessLinks.html
And also you do NOT want to store the user name + password in the connection string, but only “log on” one time. Again this saves huge hassles and also means your connection strings and/or DSN does not have to save and expose the user name and password in the actual links.
And this approach means you can have different logons and NOT have to re-link or change existing table links.
The follow shows how to use a cached logon and this thus allows one to have different logons without having to re-link your tables.
https://blogs.office.com/en-us/2011/04/08/power-tip-improve-the-security-of-database-connections/
I highly recommend you adopt both of the above approaches when using linked tables to SQL server.
This question is the first google result for "VBA create DSN", however I do not like the answers since they seem to revolve around touching the registry or otherwise avoiding the use of a DSN. In my case I have a project manager that wants to use a DSN because that is what they are comfortable with and so I could not avoid it. For anyone else struggling with this, I found a very straight forward way to do it elsewhere. Notably starting here.
I used code found there, here and here to cobble this together and put it in the open event of a splash screen form:
Private Declare Function SQLConfigDataSource Lib "ODBCCP32.DLL" _
(ByVal hwndParent As Long, ByVal fRequest As Long, _
ByVal lpszDriver As String, ByVal lpszAttributes As String) As Long
Private Sub Form_Open(Cancel As Integer)
On Error Resume Next
If fDsnExist("DSN=YOUR_DSN_NAME") = True Then
'Do all of your loading or just close this form.
Else
Dim doContinue As Integer
doContinue = MsgBox("There is an issue with the database connection. This can be corrected now or you can reach out to support." _
& vbCrLf & vbCrLf & "Do you want to attempt to correct the issue now?", vbYesNo, "Connection Error")
If doContinue = vbYes Then
Dim vAttributes As String
vAttributes = "DSN=YOUR_DSN_NAME" & Chr(0)
vAttributes = vAttributes & "Description=Self Explnatory" & Chr(0)
vAttributes = vAttributes & "Trusted_Connection=Yes" & Chr(0)
vAttributes = vAttributes & "Server=YOUR_SQL_SERVER_ADDRESS" & Chr(0)
vAttributes = vAttributes & "Database=YOUR_DATABASE_NAME" & Chr(0)
SQLConfigDataSource 0&, 1, "SQL Server", vAttributes
If Err.Number <> 0 Then
MsgBox "The connection could not be restored. Please report this error to support: " & vbCrLf & vbCrLf & Err.Description
Err.Clear
DoCmd.Close acForm, "frmSplash"
DoCmd.Quit acQuitSaveNone
Else
MsgBox "The Connection has been restored.", , "Success"
End If
Else
MsgBox "Please contact support to resolve this issue.", vbCritical + vbOKOnly, "Error"
DoCmd.Close acForm, "frmSplash"
DoCmd.Quit acQuitSaveNone
End If
End If
End Sub
Function fDsnExist(strDsn)
On Error Resume Next
' ------------------------------------------------------
' Declare Variables
' ------------------------------------------------------
Dim objConnection
Dim strReturn
' ------------------------------------------------------
' Create database object
' ------------------------------------------------------
Set objConnection = CreateObject("ADODB.Connection")
objConnection.ConnectionString = strDsn
objConnection.Open
' ------------------------------------------------------
' Check if database is open Correctly
' ------------------------------------------------------
If Err.Number <> 0 Then
strReturn = False
Err.Clear
Else
strReturn = True
' ------------------------------------------------------
' Close database connection
' ------------------------------------------------------
objConnection.Close
End If
Set objConnection = Nothing
' ------------------------------------------------------
' Return database status
' ------------------------------------------------------
fDsnExist = strReturn
End Function
Now when the user opens the access database, the splash form checks for the existence of the DSN and if it is not found, gives the user an option to create it.
You mentioned NT authentication; I use a trusted connection under the assumption that the user is already logged into a domain and has been provided access using those credentials to the SQL server. You may need to modify the DSN connection string in order to prompt for a password and username.
First and foremost, Albert D. Kallal is absolutely correct with his answer. If you can use a DSN-less connection, you should. However, for the sake of answering the question you asked...
ODBC DSN entries are stored in the windows registry. You can add by directly modifying the windows registry. I DO NOT recommend this if you are not familiar with the registry. You can brick a machine if you remove/alter the wrong keys. The particular keys we're looking for are located under Software/ODBC of HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER, depending on whether we're looking for a System or User odbc connection respectively.
My solution is too long and involved to post in it's entirety on Stack Overflow. You can find it on my blog under VBA ODBC DSN Installer complete with class module downloads and examples of how to use them. (Full disclosure, one of them was originally written by Steve McMahon, but I have modified it for use with MS Access.)
The short version is I built a DSN class on top of Mr. McMahon's registry class in order to install DSNs when my MS Access application is started.

Resources