MS Access ADO connection string with Failover Partner option - ms-access

Is it possible to use an ADO connection string in VBA Access with failover partner option included?
My connection code looks something like this:
Public Function OpenADOConnection() As Boolean
On Error GoTo err_trap
' Opens Global ADO Connection if it isnt already open.
' Returns TRUE if connected
Dim boolState As Boolean
If gcnn Is Nothing Then
Set gcnn = New ADODB.Connection ' if the global has not been instantiated or has been destroyed
End If
If gcnn.state = adStateOpen Then
boolState = True ' already open, nothing else to do
Else
gcnn.ConnectionString = "Data Source=SQL01;Failover Partner=SQL02;Initial Catalog=DBNAME;Integrated Security=True"
gcnn.Open
If gcnn.state = adStateOpen Then
boolState = True
Else
boolState = False ' cannot open connection so return false
End If
End If
OpenADOConnection = boolState ' return the connection state
exit_here:
Exit Function
err_trap:
OpenADOConnection = False
Call MsgBox("Unable to connect to the database. Please notify Database Administrator!" & vbCrLf & _
"(This error CANNOT be logged!", vbCritical, "ADO Connection Failed:", "", 0)
200 Resume exit_here
End Function
Currently, it fails to open the connection so I'm not sure what I'm missing or even if it's possible to achieve this.
Essentially I want the connection to automatically failover without user interference and knowledge.

I don't see the Provider being defined anywhere in your connection string. That is a general problem.
I never used the failover options with ADO, but I'm pretty sure the old OLE DB Provider (SQLOLEDB) does not support them. Instead you should use the brand new Microsoft OLE DB Driver for SQL Server (msoledbsql).

Related

Can't open a connection to create a transaction

In MS-Access, I'm trying to get a connection so that I can run queries within a transaction.
Stripped down, the routine looks like this:
Public Sub SetConnection()
Dim ConnectionString As String
ConnectionString = CurrentDb.TableDefs("RandomTable").Connect
If conn Is Nothing Then
Set conn = New ADODB.Connection
PostToLog "SetConnection()", "Set Connection to " & ConnectionString
conn.Open ConnectionString ' Fails here
' conn.Open ' Gives the same error without specifying the connection
End If
Exit Sub
I can see that the connection string is exactly that set in the Linked Table Manager.
Then I use it like this
Public Sub Begin()
PostToLog "Begin()", "Start Begin"
SetConnection
If Not (conn Is Nothing) Then
PostToLog "Begin()", "Begin"
conn.BeginTrans
End If
End Sub
Public Sub Commit()
PostToLog "Commit()", "Start Commit"
If Not (conn Is Nothing) Then
PostToLog "Commit()", "Committing"
conn.CommitTrans
End If
CloseConnection
End Sub
Public Function GetConnection() As ADODB.Connection
' Use GetConnection() everywhere you want a connection. If in a transaction, these queries will automatically be included.
' The exception would be items that you do NOT want rolled back in case of failure, like logging.
If conn Is Nothing Then
' Set GetConnection = New ADODB.Connection
Set GetConnection = CurrentProject.Connection
Else
Set GetConnection = conn
End If
End Function
But the connection fails to open, conn isn't set, and everything happens outside a transaction.
The error is
Unspecified error [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified: ConnectionString: ODBC;DSN=....
I've also tried
ConnectionString = CurrentProject.Connection.ConnectionString
but that looked like the connection string to the front end Access database, not the back end.
I can't find any excuse for why the linked tables can find the DSN, but can't when I create the connection.
Begin, Commit, Rollback are used like so....
objSQL.Begin ' Start transaction
' do some stuff that uses the conn connection
objSQL.Commit
Err:
objSQL.Rollback
Update:
Using conn = CurrentProject.Connection instead of creating a new ADODB connection appears to work. No errors are thrown.
But the changes do not appear, as if they all get rolled back.
Postgres doesn't allow for a dirty read, so I can't tell if the changes never happened or if they do and get reversed. And if they do get reversed, I can't tell if that is before or after the Commit or what exactly triggers it. I can see the SQL statements appearing to get executed on the Postgres connection.
I'm going to try exploring a little more what happens when I have a proper connection string, as demonstrated by #HansUp

Launch password protected database and close existing one

I am trying to set up a "Launcher" database which contains VBA code that will open a second database which is password protected. I can then convert the launcher db to accde so the VBA containing the password cannot be read.
I have the following code so far.
Private Sub Form_Load()
Dim acc As Access.Application
Dim db As DAO.Database
Dim strDbName As String
strDbName = "C:\database Folder\secureDB.accdb"
Set acc = New Access.Application
acc.Visible = True
Set db = acc.DBEngine.OpenDatabase(strDbName, False, False, ";PWD=swordfish")
acc.OpenCurrentDatabase (strDbName)
Application.Quit
End Sub
When the launcher db is opened a form loads which subsequently fires the above code. It works but the problem is the last line which is intended to close the launcher db only but closes both databases. I have also tried opening the main database using Shell but am unable to pass the password this way.
How can I close the first database while keeping the second open?
You can use the following:
Private Sub Form_Load()
Dim acc As Access.Application
Dim db As DAO.Database
Dim strDbName As String
strDbName = "C:\database Folder\secureDB.accdb"
Set acc = New Access.Application
acc.Visible = True
acc.OpenCurrentDatabase strDbName, False, "swordfish"
Set db = acc.CurrentDb() 'Don't know why you want a reference to the db
acc.UserControl = True
Application.Quit
End Sub
The relevant part is acc.UserControl = True, that forces the DB to stay visible and stops it from closing as soon as the reference to the Application object gets destroyed.
A sample database that stores the main database password encrypted with a salted user password can be found in this answer
I was having trouble getting the accepted answer to work properly. I was able to make work with:
Public Function OpenAccessDb(strVerPath, strFileName, sRecordset, strPwd)
'You may also need to have the following References Added:
'Microsoft Access 16.0 Object Library & Microsoft Office 16.0 Access Database Engine Object
'Visual Basic for Applications// Microsoft Excel 16.0 Object Library// OLE Automation//
'Microsoft Forms 2.0 Object Library// Microsoft Outlook 16.0 Object Library// Microsoft Office 16.0 Object Library
Dim oDAO As DAO.DBEngine, oDB As DAO.Database, oRS As DAO.Recordset
Dim sPath As String
'sPath = GetProperDirectory(strVerPath, strFileName) ' you can bypass this function by setting the path manually below and commenting this out.
sPath = "C:\database Folder\secureDB.accdb"'manually set the path here and comment out line above
Set oDAO = New DAO.DBEngine
Set oDB = oDAO.OpenDatabase(sPath, False, True, "MS Access;PWD=" & strPwd)
Set oRS = oDB.OpenRecordset(sRecordset)
''paste to call this function
''note this function utilizes the GetProperDirectory function.
''The GetProperDirectory function uses xxxxx as the location source
''therefore the strVerPath should start after \xxxxx\yyyyyy\yyyyy\DB.accdb
'strVerPath = "\yyyyyy\yyyyy\"
'strFileName= "DB.accdb"
'sRecordSet= "table in access DB" 'the table you are sending the data to
'strPwd = "password' 'this is the password that allows access to the database
'booOpenSend= OpenAccessDb(strVerPath, strFileName, sRecordSet, strPwd)
''end paste
End Function

Tell me if invalid SQL login, but continue with script... How?

I want my script to tell me, if the login to the database has failed with a custom response.write() and then continue with my script. Is this possible?
Say I have this:
set conn = server.createobject("adodb.connection")
dsn = "Provider = sqloledb; Data Source = XX; Initial Catalog = XX; User Id = XX; Password = XX
conn.connectiontimeout = 300
conn.commandtimeout = 300
if conn.state = 0 then
conn.open dsn
end if
If the conn couldn't be opened because of bad Data Source, User Id or Password, then I want it write me a message with response.write() and then CONTINUE with the rest of the script, or else it should do my sql-actions AND THEN continue :)
on error resume next
conn.open dsn
if err.number <> 0 then
response.write "connection string was bad"
else
' other SQL activity here
end if
on error goto 0
You can use On Error Resume Next
This page talks about handling connection errors and gives a pretty good example of what you are talking about.
http://www.codeguru.com/csharp/.net/net_general/debugginganderrorhandling/article.php/c19557/ASP-and-the-Error-Handler.htm

Dynamically changing MySQL connection string to Crystal Reports

I am using CrystalReportViewer and CrystalReportSource to load and display an .rpt file in my application.
The situation I have is this:
Say a person created a Crystal Reports report outside of my application and set its datasource to database. Then I use that .rpt file in my application, but I need to bind it to a different database (identical to the original one in terms of table structure and column names but with a different connection string and user name and password).
How do I do that in VB.NET code?
Currently I load the report using:
Public Function SetReportSource(ByVal RptFile As String) As ReportDocument
Try
Dim crtableLogoninfo As New TableLogOnInfo()
Dim crConnectionInfo As New ConnectionInfo()
Dim CrTables As Tables
Dim CrTable As Table
If System.IO.File.Exists(RptFile) Then
Dim crReportDocument As New ReportDocument()
crReportDocument.Load(RptFile)
With crConnectionInfo
.ServerName = "DRIVER={MySQL ODBC 5.1 Driver};SERVER=localhost;Port=3306;UID=root;"
.DatabaseName = gDatabaseName
.UserID = gServerUser
.Password = gServerPassword
End With
CrTables = crReportDocument.Database.Tables
For Each CrTable In CrTables
CrTable.ApplyLogOnInfo(crtableLogoninfo)
CrTable.LogOnInfo.ConnectionInfo.ServerName = crConnectionInfo.ServerName
CrTable.LogOnInfo.ConnectionInfo.DatabaseName = crConnectionInfo.DatabaseName
CrTable.LogOnInfo.ConnectionInfo.UserID = crConnectionInfo.UserID
CrTable.LogOnInfo.ConnectionInfo.Password = crConnectionInfo.Password
'Apply the schema name to the table's location
CrTable.Location = gDatabaseName & "." & CrTable.Location
Next
crReportDocument.VerifyDatabase()
SetReportSource = crReportDocument
Else
MsgBox("Report file not found...", MsgBoxStyle.Critical, proTitleMsg)
End If
Catch ex As Exception
System.Windows.Forms.MessageBox.Show("Error Found..." & vbCrLf & "Error No : " & Err.Number & vbCrLf & "Description :" & Err.Description & vbCrLf & vbCrLf & "Line no : " & Err.Erl & vbCrLf & "Procedure name : SetReportSource" & vbCrLf & "Module name : GeneralFunctions", proTitleMsg)
End Try
End Function
This is how I did it. I was using Oracle with ODBC on ASP.NET, but you should be able to do the same with MySQL and ODBC:
As part of the legacy application upgrade I've been doing, I decided to move the Crystal Reports to a Web application rather that having the users access them directly on Crystal Reports XI via Citrix, which has been the method they have been using.  This has several advantages, the primary one being speed.  One problem that plagued me was how to change the logon information at run-time, such that the application would automatically point at the correct Oracle database (development, test, or production) based on which server the report was being accessed from.
The solution I found was to set up the Oracle Database connection as a DSN in ODBC, and connecting to the ODBC DSN rather than using the Oracle Client directly.  This is not the only way to do it, but it seems to be the best way for my purposes.
In the code-behind file for the page that contains the Crystal Reports Viewer, I placed the following code that handles the same event that renders the viewer.
Protected Sub btnGenerate_Click(sender As Object, e As System.EventArgs) Handles btnGenerate.Click
Dim connInfo As New ConnectionInfo
Dim rptDoc As New ReportDocument
' setup the connection
With connInfo
.ServerName = "oracledsn" ' ODBC DSN in quotes, not Oracle server or database name
.DatabaseName = "" ' leave empty string here
.UserID = "username" ' database user ID in quotes
.Password = "password"  'database password in quotes
End With
' load the Crystal Report
rptDoc.Load(Server.MapPath(Utilities.AppSettingsFunction.getValue("ReportFolder") & ddlReports.SelectedValue))
' add required parameters
If pnlstartdates.Visible Then
rptDoc.SetParameterValue("REPORT_DATE", txtSingleDate.Text)
End If
' apply logon information
For Each tbl As CrystalDecisions.CrystalReports.Engine.Table In rptDoc.Database.Tables
Dim repTblLogonInfo As TableLogOnInfo = tbl.LogOnInfo
repTblLogonInfo.ConnectionInfo = connInfo
tbl.ApplyLogOnInfo(repTblLogonInfo)
Next
' Set, bind, and display Crystal Reports Viewer data source
Session("rptDoc") = rptDoc
Me.CrystalReportViewer1.ReportSource = Session("rptDoc")
CrystalReportViewer1.DataBind()
UpdatePanel1.Update()
End Sub
The logon info above can easily be stored in web.config instead of hard-coding it as above.
Incidentally, I chose to put my Crystal Reports Viewer in an ASP.NET AJAX Update Panel, which is why the ReportSource of the viewer is stored in a Session variable.  If you choose to do this, the viewer must be databound in the Init event (not the Load event) to show up properly.
Protected Sub Page_Init(sender As Object, e As System.EventArgs) Handles Me.Init
If Not Page.IsPostBack Then
txtSingleDate.Text = Now.Date()
ElseIf Session("rptDoc") IsNot Nothing Then
Me.CrystalReportViewer1.ReportSource = Session("rptDoc")
CrystalReportViewer1.DataBind()
UpdatePanel1.Update()
End If
End Sub

Access: persist a COM reference across Program Reset?

Are there ways in Access VBA (2003) to cast a COM reference to an integer, and to call AddRef/Release? (which give the error "Function or interface marked as restricted, or the function uses an Automation type not supported in Visual Basic")
I'm using a third-party COM object which doesn't handle being instantiated twice in a single process (this is a known bug). I therefore thought of storing the reference as the caption of a control on a hidden form to protect it from Program Reset clearing all VB variables.
Edit: I think the cast to int can be done with the undocumented ObjPtr, and back again with the CopyMemory API, and AddRef/Release can be called implicitly. But is there a better way? Are add-ins protected from Program Reset?
Is the problem with surviving the code reset or is it that once the code is reset it can't be re-initialized?
For the first problem, wrap your top-level object in a function and use a STATIC variable internally to cache the reference. If the STATIC variable Is Nothing, re-initialize. Here's the function I use for caching a reference to the local database:
Public Function dbLocal(Optional bolInitialize As Boolean = True) +
As DAO.Database
' 2003/02/08 DWF added comments to explain it to myself!
' 2005/03/18 DWF changed to use Static variable instead
' uses GoTos instead of If/Then because:
' error of dbCurrent not being Nothing but dbCurrent being closed (3420)
' would then be jumping back into the middle of an If/Then statement
On Error GoTo errHandler
Static dbCurrent As DAO.Database
Dim strTest As String
If Not bolInitialize Then GoTo closeDB
retryDB:
If dbCurrent Is Nothing Then
Set dbCurrent = CurrentDb()
End If
' now that we know the db variable is not Nothing, test if it's Open
strTest = dbCurrent.Name
exitRoutine:
Set dbLocal = dbCurrent
Exit Function
closeDB:
If Not (dbCurrent Is Nothing) Then
Set dbCurrent = Nothing
End If
GoTo exitRoutine
errHandler:
Select Case err.Number
Case 3420 ' Object invalid or no longer set.
Set dbCurrent = Nothing
If bolInitialize Then
Resume retryDB
Else
Resume closeDB
End If
Case Else
MsgBox err.Number & ": " & err.Description, vbExclamation, "Error in dbLocal()"
Resume exitRoutine
End Select
End Function
Anywhere you'd either of these in code:
Dim db As DAO.Database
Set db = CurrentDB()
Set db = DBEngine(0)(0)
db.Execute "[SQL DML]", dbFailOnError
...you can replace the whole thing with:
dbLocal.Execute "[SQL DML]", dbFailOnError
...and you don't have to worry about initializing it when your app opens, or after a code reset -- it's self-healing because it checks the Static internal variable and re-initializes if needed.
The only caveat is that you need to make a call with the bolInitialize argument set to False when you shut down your app, as this cleans up the reference so there's no risk of your app hanging when it goes out of scope as the app closes.
For the other problem, I really doubt there's any solution within VBA, unless you can make an API call and kill the external process. But that's something of a longshot, I think.