Copyright 1994-97 Paul D. Sheriff & Associates
All Rights Reserved Worldwide, Reproduction is strictly prohibited.
Creating reusable classes that you can use in every database application is a great productivity tool. Of course, you must take some time up front to develop these; the payoff will come on your next application. There are many examples of reusable classes you can create. In this course, you will learn about a couple of them.
A data access class is a wrapper around all of the things you may do to a base table. Adding, editing, deleting and retrieving records will all become methods that you invoke. You can even add functionality such as specifying the WHERE clause and the ORDER BY clause. Once you have created one of these classes, you will never have SQL statements all over your source code again. Instead, you will always be instantiating an object, setting some properties, and invoking some methods. You will find that by doing this, you will greatly speed up your development time, cut down the number of bugs, and greatly reduce the maintenance time.
The BARK sample application uses many different classes. There will be classes that are generic that can be used in many applications, and there are others that are specific to the BARK application. In this section, you will learn how to create some of the generic classes that you will be able to use and reuse in all of your applications. The following is a list of the generic functions you will learn to create in this chapter.
In every database application you design, you will generally need to keep track of several pieces of information to make that connection:
Most programmers will keep this information in separate global variables. Next you would then have some public functions that perform the following services:
Each of the above functions relies on one or more of the global variables you defined above. All of this information taken together is an ideal candidate for a class. You have the global variables, which can become properties, and the functions that act upon those global variables (properties), which can become the methods.
The name of this database class is DBConnect. To create an instance of this Database Connection Class, you would use the following code:
Public goConnect As New DBConnect
The Database Connection Class you create will be for one type of database access method. Depending on the database access methods you want to use, you will need to create a Database Connection Class for each one. The class you will see in this chapter is the one for Jet.
The DBConnect Class will have both private variables and corresponding public properties that are implemented using Property Get and Property Let procedures. Below is a list of each of the public properties that you will use to reference the data from your global database connection object:
Public Name | Private Name | Description |
ConnectString | NA | Builds an ODBC connect string using other variables within this object. |
DBPath | pstrDBPath | The path of the local MDB. |
DBName | pstrDBName | The name of the local MDB. |
DatabaseName | pstrDatabase | The full name of the local MDB or the name of the server database. |
DBObject | pdb | The database object. |
DSN | pstrDSN | The Data Source Name. |
InfoCode | plngInfoCode | Returns the last error code generated within this class. |
InfoMsg | pstrInfoMsg | Returns the last error message generated within this class. |
LoginID | pstrLoginID | The login id used to login to the data source. |
OnServer | pboolServer | Are you on a database server or on a local MDB? |
Password | pstrPassword | The password used to login to the data source. |
RecordsAffected | pintRecords | The number of records affected by the last action query. |
SystemDB | pstrSystemDB | The name of the System database for a secured Jet database. |
ValidLogon | pboolValidLogon | Have you made a valid connection to the database? |
UserID | pstrUserID | The local MDB. |
WSObject | pws | The workspace object. |
There are some methods that you will need to use to make the connection to the database. Below is a table listing each of the method names and the description of each:
Method Name | Description |
ConfigGet | Retrieves information from the registry for this application about the DSN, Database Path, Database Name, and the last LoginID. |
ConfigSave | Saves the information to the registry. |
DataClose | Close the database connection. |
DataInit | Initialize the database engine. |
DataOpen | Opens the database connection. |
Execute | Executes action queries. |
To use the database connection class, you need to invoke certain methods at certain times. Below is a sample of how you would use the Database Connection Class:
Dim boolPerform As Boolean boolPerform = goConnect.ConfigGet() ' Initialize the Database Engine If boolPerform Then If Not goConnect.OnServer Then boolPerform = goConnect.DataInit() End If End If ' Open Connection to Database If boolPerform Then If Not goConnect.OnServer Then boolPerform = goConnect.DataOpen() End If Else Beep MsgBox goConnect.InfoMsg End If
You first call the ConfigGet() method to retrieve all the appropriate information from the registry. You then have to initialize the database engine, and then you can open a connection to the database.
The ConfigGet() method will retrieve the base information needed to make a connection to a database. Below is some of the code for this method:
Public Function ConfigGet() As Boolean Dim strLoc As String Dim vntArray As Variant Dim boolPerform As Boolean On Error GoTo ConfigGet_EH vntArray = GetAllSettings(App.EXEName, "DataSource") If IsEmpty(vntArray) Then frmConfig.Show vbModal boolPerform = frmConfig.Saved Else boolPerform = True End If If boolPerform Then ' Get Location of Data Source Info strLoc = GetSetting(App.EXEName, _ "DataSource", _ "DataLoc", "") ' Get the Database Path pstrDBPath = GetSetting(App.EXEName, _ strLoc, _ "DBPath", "") … … ConfigGet = True Else ConfigGet = False End If Exit Function ConfigGet_EH: ConfigGet = False pstrInfoMsg = Err.Description plngInfoCode = Err.Number Exit Function End Function
You have now seen how to retrieve the majority of the database connection information from the registry. Now you need to use that information to make the connection to the database. DataInit() is the method you will call after calling the ConfigGet() method of the Database Connection Class. The DataInit() method for the Jet engine will initialize the Workspace object. Below is the code for the DataInit() method:
Public Function DataInit() As Integer On Error GoTo DataInit_EH If pws Is Nothing Then Set pws = DBEngine.CreateWorkspace("MultiTierApp", _ pstrLoginID, _ pstrPassword, _ dbUseJet) DataInit = True End If Exit Function DataInit_EH: pboolValidLogon = False pstrInfoMsg = Err.Description DataInit = False Exit Function End Function
Notice how you are creating a generic name for this method instead of something like an OpenWorkspace() method. The reason is you want to create a method name that is generic across many different data access methods. In ADO or in RDO, there is no Workspace object. So an OpenWorkspace() method name would not make sense. By using DataInit(), you can write your Sub Main() to call this routine, and if you change the database connection object to use ADO or RDO, you don’t have to change the Sub Main(), just the DataInit() method.
After you have initialized the Workspace object, you then attempt to open the connection to the database by opening a database object. The DataOpen() method is called after DataInit() to establish the connection with the database:
Public Function DataOpen() As Integer Dim strConnect As String On Error GoTo DataOpen_EH If pdb Is Nothing Then DataOpen = True If pboolServer Then ' Logon to ODBC Data Source strConnect = "ODBC" & Me.ConnectString Set pdb = pws.OpenDatabase("", False, _ False, strConnect) Else ' Logon to Local Access Database Set pdb = pws.OpenDatabase(pstrDatabase) End If End If DataOpen = True pboolValidLogon = True Exit Function DataOpen_EH: DataOpen = False pstrInfoMsg = Err.Description pboolValidLogon = False Exit Function End Function
The above method will either open a local database or make a connection to an ODBC data source. By checking the private variable pboolServer, you can determine which OpenDatabase() method to call. After calling the appropriate routine, you now have a Database object and a Workspace object encapsulated within this object.
To return the Database object when using this routine, you will use the DBObject property. Below is the Property Get procedure for the DBObject property:
Property Get DBObject() As Database Set DBObject = pdb End Property
To use this property from anywhere in your source code, you will need to reference the public variable goConnect, then the DBObject property of that object. Below is the code you would use to open a Recordset object:
Dim oRS As Recordset Dim strSQL As String
strSQL = "SELECT * FROM tblDogs"
Set oRS = goConnect.DBObject.OpenRecordset(strSQL, _ dbOpenSnapshot)
In the code for the DataOpen() routine, you will find a call to a method of the Database Connection Class called ConnectString(). This routine will build an ODBC connection string from information in the connection class:
Property Get ConnectString() As String Dim strConn As String strConn = strConn & ";DSN=" & pstrDSN strConn = strConn & ";UID=" & pstrLoginID strConn = strConn & ";PWD=" & pstrPassword strConn = strConn & ";Database=" & pstrDatabase strConn = strConn & ";WSID=" & App.EXEName & _ "(" & pstrLoginID & ")" ConnectString = strConn End Property
To build the connection string, you will need to know which variables your database server requires. The above connection string will work with SQL ServerÔ. Below is an example of what this connection string might look like.
DSN=Dogs;UID=sa;PWD=;Database=Dogs;WSID=MultiApp(sa)
Notice the use of the WSID at the end of the string. This WorkStation ID is passed to SQL Server so that a system administrator can see which connections are being used by which applications and which users. To see this information, execute the sp_who system procedure in SQL Enterprise Manager.
Many times you may want to submit an SQL statement such as INSERT, UPDATE, or DELETE. These action queries do not return any values, so you can use the Database objects’ Execute() method. In your connection class, you should encapsulate the Database objects Execute() method so that you can duplicate this functionality for other database access methods. Below is the code for the Execute method of the Database Connection Class:
Function Execute(strSQL As String) As Integer On Error GoTo Execute_EH ' Execute the SQL Statement pdb.Execute strSQL ' Get number of records affected pintRecords = pdb.RecordsAffected Execute = True Exit Function Execute_EH: Execute = False pstrInfoMsg = Err.Description plngInfoCode = Err.Number Exit Function End Function
You will pass in an SQL statement to be executed to this method. It will then perform the SQL and store the number of records affected by the query in the RecordsAffected property of the database connection object. Below is an example of how you might call this routine:
… strSQL = "DELETE FROM tblDogs " ' Submit the SQL Statement If goConnect.Execute(strSQL) Then If goConnect.RecordsAffected = 0 Then MsgBox "SQL statement did not change any records" End If End If …
If the Execute() method is successful, a True value is returned. However, this does not necessarily mean any records were affected by this query. If you want to see if any records were affected, you need to check the RecordsAffected property.
When you are finished with a database connection, you should close it. This helps free up some client side and server side resources. Ideally you would open a connection, perform some work, then close the connection. You would constantly be opening, closing, and reopening a connection. However, because of performance, you will generally open a connection one time and leave that connection open. The DataClose() method is what you will use to close the database connection:
Public Function DataClose() As Integer On Error Resume Next If Not (pdb Is Nothing) Then pdb.Close Set pdb = Nothing End If If Not (pws Is Nothing) Then pws.Close Set pws = Nothing End If End Function
Let’s now create another class to interact with databases called a Recordset Class. Some may think this is unnecessary. After all, Jet already has a Recordset object that you can use. While this is true for Jet, not all database access methods have a Recordset object. Some do not even have objects at all. For example, ADO uses an ADODB.Recordset, but RDO uses a Resultset, and the ODBC API simply uses a long integer. Therefore, you need to build a common Recordset Class that you can wrap all of these methods into so that you don’t have to change any other source code in your system; just this one class.
The class name for the Recordset Class is DBRecordset. To create an instance of this class, you would use the following code:
Dim oRS As New DBRecordset
Let’s now look at some of the standard private variables in this class that you will generally use in each application. You will also see the public interface for these properties and some of the methods of this class.
The DBRecordset Class has both private variables and corresponding public properties that are implemented using Property Get and Property Let procedures. Other variables are used internally by the Recordset Class itself. Below is a list of each of the public properties that you will use to reference the data from your recordset object:
Public Name | Private Name | Description |
Connection | poConnect | A reference to the DBConnect object stored in the DBRecordset object. |
DBObject | NA | A reference to the DBConnect.DBObject property. |
EOF | pboolEOF | Is the Recordset at the end of file? |
InfoCode | plngInfoCode | Returns the last error code generated within this class. |
InfoMsg | pstrInfoMsg | Returns the last error message generated within this class. |
LockType | pintLockType | Return or set the lock type to use for the Recordset object. |
Options | pintOptions | Return or set the options to use for the Recordset object. |
RSType | pintRSType | Used to set the type of Recordset. You may set this to one of the valid Jet Recordset types such as dbOpenSnapshot, dbOpenDynaset, etc. |
There are many methods in the DBRecordset Class that you can use to manipulate the Recordset object itself. Below is a table listing each of the method names and the description of each:
Method Name | Description |
BooleanData | Returns a column that contains a Boolean data type. |
CloseRecordset | Closes an open set of records. |
ConcurrencyData | Returns a column that contains a Single data type. |
CurrencyData | Returns a column that contains a Currency data type. |
DateData | Returns a column that contains a Date data type. |
DateStringData | Returns a column that contains a Date data type, but returns it as a string. |
DoubleData | Returns a column that contains a Double data type. |
Find | Finds a particular row(s) based on an SQL string passed to this method. |
IntegerData | Returns a column that contains a Integer data type. |
LongData | Returns a column that contains a Long data type. |
MemoData | Returns a column that contains a Memo data type. |
MoveFirst | Moves to the first row in the record set. |
MoveLast | Moves to the last row in the record set. |
MoveNext | Moves to the next row in the record set. |
MovePrevious | Moves to the previous row in the record set. |
OpenRecordset | Opens a record set based on an SQL string passed in. |
SingleData | Returns a column that contains a Single data type. |
StringData | Returns a column that contains a String data type. |
VarData | Returns any column as a Variant data type. |
To use this DBRecordset object, you will first need to declare the object, then you can use it with syntax very similar to Jet.
Dim oRS As New DBRecordset Dim strSQL As String Dim strName As String Dim lngID As Long strSQL = "SELECT szDog_nm, lDog_id FROM tblDogs" oRS.OpenRecordset(strSQL) Do Until oRS.EOF strName = oRS.StringData() lngID = oRS.LongData() oRS.MoveNext Loop oRS.CloseRecordset
You can see that this code looks very similar to Jet. However, you have to retrieve the columns in the exact order you specified them in the SELECT statement. This will allow you to use this DBRecordset across all data access methods. If you want to use a new method for accessing data, such as ADO, you only need to change the DBRecordset object, not all of the source code in your project.
When you create an instance of the DBRecordset Class, the Class_Initialize() event will automatically set the connection object to the value of the global connection object. This can always be overriden if you have another connection object you want to use. It will also assume you want a Snapshot type of Recordset object created:
Private Sub Class_Initialize() Set poConnect = goConnect pintRSType = dbOpenSnapshot pintOptions = dbForwardOnly pintLockType = dbReadOnly End Sub
To open a set of records against a specific table, you will need to call the OpenRecordset() method just like you would in Jet. You need to pass in an SQL statement and optionally any record set type, options, and lock type you want. Below is the source code for the OpenRecordset() method:
Public Function OpenRecordset(ByVal strSQL As String, _ Optional intRSType As Integer = -1, _ Optional intOptions As Integer = -1, _ Optional intLockType As Integer = -1) _ As Boolean Dim intStatus As Integer On Error GoTo OpenRecordset_EH ' Reset Column Number pintColumn = 0 ' Check Optional Parameters If intRSType <> -1 Then pintRSType = intRSType End If If intOptions <> -1 Then pintOptions = intOptions End If If intLockType <> -1 Then pintLockType = intLockType End If ' Clear Informational/Error Messages Call ClearMessages ' Open Result Set Set poRS = _ poConnect.DBObject.OpenRecordset(strSQL, _ pintRSType, _ pintOptions, _ pintLockType) If poRS.EOF Then Call CloseRecordset OpenRecordset = False Else OpenRecordset = True pboolRSActive = True End If Exit Function OpenRecordset_EH: pintInfoCode = Err.Number pstrInfoMsg = Err.Description OpenRecordset = False Exit Function End Function
Notice how the pintColumn variable is set to 1. This allows you to just call one of the *Data() methods to retrieve the appropriate data type for the 1st column.
There are many methods that return column data. You may return each column as a specific data type, or you may simply choose to return all columns as a variant. Below are two of the *Data() methods:
Public Function VarData(Optional intColumn As Variant) _ As Variant On Error GoTo Var_EH If Not IsMissing(intColumn) Then pintColumn = intColumn End If ' Retrieve Column Data VarData = poRS(pintColumn) ' Increment The Column Number pintColumn = pintColumn + 1 Exit Function Var_EH: pintInfoCode = Err.Number pstrInfoMsg = Err.Description VarData = False Exit Function End Function
Here is the function to return LongData():
Public Function LongData(Optional intColumn As Variant) _ As Long On Error GoTo Long_EH If Not IsMissing(intColumn) Then pintColumn = intColumn End If ' Retrieve The Data LongData = Field2Long(poRS(pintColumn)) ' Increment the Column Number pintColumn = pintColumn + 1 Exit Function Long_EH: pintInfoCode = Err.Number pstrInfoMsg = Err.Description LongData = False Exit Function End Function
There are four methods that you can use to move the record pointer in various directions. MoveNext(), MovePrevious(), MoveFirst(), and MoveLast() are the method names. These are pretty self-explanatory, so let’s just look at a couple:
Public Function MoveNext() As Boolean Dim intStatus As Integer MoveNext = False On Error GoTo MoveNext_EH If pboolRSActive Then ' Reset Column Number pintColumn = 0 ' Move To Next Row poRS.MoveNext If Not poRS.EOF Then pboolEOF = True MoveNext = True Else pboolEOF = False MoveNext = False End If End If Exit Function MoveNext_EH: pintInfoCode = Err.Number pstrInfoMsg = Err.Description pboolEOF = False MoveNext = False Exit Function End Function
Here is the MoveFirst() method:
Public Function MoveFirst() As Boolean On Error GoTo MoveFirst_EH If pboolRSActive Then poRS.MoveFirst MoveFirst = True End If Exit Function MoveFirst_EH: pintInfoCode = Err.Number pstrInfoMsg = Err.Description MoveFirst = False Exit Function End Function
When you are through with a Recordset object, you should always close that record set. Unfortunately, in the Microsoft® Visual Basic® programming system, Close, is a reserved word, and you may not use reserved words for method names. Thus, you will need to name this method CloseRecordset():
Public Sub CloseRecordset() Dim intStatus As Integer On Error Resume Next If pboolRSActive Then ' Release Result Set poRS.Close Set poRS = Nothing pboolRSActive = False End If ' Clear Information/Error Messages Call ClearMessages End Sub
Below is a table of the services our data access classes should accomplish for each of the base tables contained in our database application.
Method | Description |
AddNew | Performs an INSERT to add a new row to the table. |
Check | Check all the data in the properties prior to saving the data. Returns a True if everything is OK. If something is in error, it will fill in a property with a message describing the error. |
CloseRecordSet | Close the recordset created by this class. |
Delete | Performs a DELETE to remove a row from the table based on the primary key. |
DeleteByFilter | Performs a DELETE to remove a row(s) from the table based on any WHERE filter you specify. |
FilterGet | Returns a WHERE clause to be added to the end of a SELECT or DELETE statement. |
Find | Find a particular row based on the primary key. |
FindByFilter | SELECTs a row(s) from the table based on any WHERE filter you specify. |
MoveNext | Moves to the next row in the recordset. |
OpenRecordset | Opens a recordset for moving through. Can be generated with a particular filter by passing in a specified string. |
Replace | Performs an UPDATE on a row in the table. |
Retrieve | Retrieves the fields from the table and loads properties in the object with the current row in the recordset. |
SelectSQL | Returns the SQL statement that will load all of the columns in the table and create a recordset out of these columns. |
SortGet | Returns an ORDER BY clause to be added to the end of a SELECT statement. |
Besides the standard methods that perform actions, you will also have several properties that hold information about the object. These include a reference to a connection object and an instance of the DBRecordset object. A filter name for adding a WHERE clause. A filter name for adding an ORDER BY. A filter name for the SELECT statement to use:
' Private Connection and Recordset Private poConnect As DBConnect Private poRS As New DBRecordset ' Filters for WHERE, ORDER BY and SELECT Private pstrFilterName As String Private pstrSortName As String Private pstrSelectFilter As String ' For Info & Error Handling Messages Private pstrInfoMsg As String Private pintInfoCode As Integer
All of the rest of the properties in your data access classes will be unique based on the underlying base table the class is based on. In this section, you will look at a class that deals with a dog information table.
There is a table in our dogs database called tblDogs. This table holds various information about the dog in the application. Below is the structure for this table:
Column Name | Description |
lDog_id | Primary key of dog. |
szDog_nm | The name of the dog. |
lBreed_id | Foreign key to breed table. |
lBreeder_id | Foreign key to breeder table. |
szColor_nm | The color of the dog. |
sBark_type | What the dog’s bark sounds like. |
cCost_amt | The cost of the dog. |
cPrice_amt | The selling price of the dog. |
dtBirth_dt | The birth date of the dog. |
sSex_nm | The sex of the dog. |
sLastUpdate_id | Last user to update this row. |
dtLastUpdate_dt | Last date this row was updated. |
iConcurrency_id | Concurrency id. |
In our Data Access Class, you will need private variables, and Property Get and Property Let procedures that correspond to each of the column names in the above table. You should use hungarian notation for the private variables, but make meaningful names for the properties. Below is a table showing each of the columns, public names, and private names:
Column Name | Public Properties | Private Variables |
lDog_id | DogID | plngDogID |
szDog_nm | DogName | pstrDogName |
lBreed_id | BreedID | plngBreedID |
lbreeder_id | BreederID | plngBreederID |
sColor_nm | ColorName | pstrColorName |
sBark_type | BarkType | pstrBarkType |
cCost_amt | CostAmount | pcurCostAmount |
cPrice_amt | PriceAmount | pcurPriceAmount |
dtBirth_dt | BirthDate | pdtBirthDate |
sSex_nm | Sex | pstrSex |
sLastUpdate_id | N/A | pstrLastUpdateID |
dtLastUpdate_dt | N/A | pdtLastUpdateDt |
iConcurrency_id | N/A | pintConcurrencyID |
The first standard method you will create for your Data Access Class is one that will create a result set of data from our underlying base table. This method will use other properties and methods of the class to perform its job:
Public Function OpenRecordset() As Boolean Dim strSQL As String Screen.MousePointer = vbHourglass ' Initialize Variables OpenRecordset = False pstrInfoMsg = "" ' Set Connection Set poRS.DBObject = poConnect ' Build Select Statement strSQL = SelectSQL() ' Build Recordset Object If poRS.OpenRecordset(strSQL) Then ' Retrieve the info Call Retrieve OpenRecordset = True Else Call CloseRecordset OpenRecordset = False ' Get Any Error Information pstrInfoMsg = poRS.InfoMsg pstrInfoCode = poRS.InfoCode End If Screen.MousePointer = vbDefault End Function
This method first needs to take the connection object from itself and give it to its DBRecordset object. This will ensure that both this object and the Recordset object are using the same connection. This is only needed if you will be making multiple connections to one or to many different database sources.
Next, this method will need to create the SELECT statement to create the set of records. You will learn how to build this SELECT statement later.
After you have the SELECT statement, you can now pass this SELECT statement to the OpenRecordset() method of the DBRecordset object you created. This routine will use the current data access method to create a result set. If the result set is created correctly, a True value will be returned. If it is not created correctly, a False value will be returned. You can then get any error messages and numbers by checking the InfoMsg and InfoCode properties of the DBRecordset object.
Notice that the OpenRecordset method uses a poConnect object. This is set in the Class_Initialize() for this class to the value of goConnect. There is a Connection property that you may use to override this connection with another connection.
Private Sub Class_Initialize() Set poConnect = goConnect End Sub
The SelectSQL is a private method that will return a SELECT statement based on the SelectFilter property. You can create as many SELECT CASE statements as you want for however many SELECT statements you want to use. Below is the source code for the SelectSQL method:
Private Function SelectSQL() As String Dim strSQL As String Select Case UCase$(pstrSelectFilter) Case "LISTBOX" strSQL = "SELECT " strSQL = strSQL & "lDog_id, " strSQL = strSQL & "szDog_nm " strSQL = strSQL & "FROM tblDogs " Case "LISTBREEDLOAD" strSQL = "SELECT lDog_id, " strSQL = strSQL & "szDog_nm, " strSQL = strSQL & "szBreed_nm " strSQL = strSQL & "FROM tblDogs, tblBreed " strSQL = strSQL & "WHERE tblDogs.lBreed_id = tblBreed.lBreed_id " Case Else strSQL = "SELECT " strSQL = strSQL & "lDog_id, " strSQL = strSQL & "szDog_nm, " strSQL = strSQL & "lBreed_id, " strSQL = strSQL & "lBreeder_id, " strSQL = strSQL & "szColor_nm, " strSQL = strSQL & "sBark_type, " strSQL = strSQL & "cCost_amt, " strSQL = strSQL & "cPrice_amt, " strSQL = strSQL & "dtBirth_dt, " strSQL = strSQL & "sSex_nm, " strSQL = strSQL & "sLastUpdate_id, " strSQL = strSQL & "dtLastUpdate_dt, " strSQL = strSQL & "iConcurrency_id " strSQL = strSQL & "FROM tblDogs " End Select ' Add Filter To This SELECT Statement strSQL = strSQL & FilterGet() ' Add Sort To This SELECT Statement strSQL = strSQL & SortGet() SelectSQL = strSQL End Function
After checking the pstrSelectFilter variable (SelectFilter property), a SELECT statement is built. Next the FilterGet() method is called to add any WHERE clause that may have been requested. Finally the SortGet() method is called to add any ORDER BY clause that may have been requested.
The FilterGet() method is another method that can change over time as you decide you need to retrieve your data using different critieria. Below is the code for the FilterGet() method:
Private Function FilterGet() As String Dim strSQL As String ' Select Records using Requested Filter Select Case UCase$(pstrFilterName) Case "PRIMARYKEY" strSQL = strSQL & " WHERE lDog_id = " & plngDogId Case "DOGNAME" strSQL = strSQL & " WHERE szDog_nm = " & Str2Field(pstrDogName) Case "BREEDID" strSQL = strSQL & " WHERE lBreed_id = " & plngBreedId Case "BREEDID_BREED" strSQL = strSQL & " AND tblDogs.lBreed_id = " & plngBreedId Case "BREEDERID" strSQL = strSQL & " WHERE lBreeder_id = " & plngBreederId Case "" ' Do Nothing Case Else MsgBox "clsDogs Can't Find Filter Passed: " & pstrFilterName End Select FilterGet = strSQL End Function
You may use this method for WHERE clauses for SELECTs or DELETEs. You can create any WHERE clause you want within each CASE statement.
The SortGet() method is a method you can modify as you decide you need additional ordering of your result sets. Below is the code for the SortGet() method:
Private Function SortGet() As String Dim strSQL As String ' Add ORDER BY Select Case UCase$(pstrSortName) Case "DOGNAME" strSQL = strSQL & " ORDER BY szDog_nm" End Select SortGet = strSQL End Function
The MoveNext() method is used to move to the next row in the result set, then call the Retrieve() method to load the current row of the result set into the private variables in the object. This method returns a False when the end of the file is hit:
Public Function MoveNext() As Boolean ' Move to Next Record If poRS.MoveNext() Then ' Load Properties Call Retrieve MoveNext = True Else MoveNext = False Call CloseRecordset pstrInfoMsg = poRS.InfoMsg End If End Function
The first call is to the DBRecordset object to have it move to the next row in the result set. If this method returns a True value, then the Retrieve() method is called to take the data from the result set and move it into the private variables.
The retrieve method will use the SelectFilter filter in conjunction with the SelectSQL() method. The number of columns you are selecting must match the number of columns you are retrieving. If you change your SELECT statement, you must remember to change your retrieval method also:
Private Sub Retrieve() With poRS Select Case UCase(pstrSelectFilter) Case "LISTBOX" plngDogId = .LongData() pstrDogName = .StringData() Case "LISTBREEDLOAD" plngDogId = .LongData() pstrDogName = .StringData() pstrBreedName = .StringData() Case Else plngDogId = .LongData() pstrDogName = .StringData() plngBreedId = .LongData() plngBreederId = .LongData() pstrColorName = .StringData() pstrBarkType = .StringData() pcurCostAmount = .CurrencyData() pcurPriceAmount = .CurrencyData() pstrBirthDate = .StringData() pstrSex = .StringData() pstrLastUpdateId = .StringData() pstrLastUpdateDate = .StringData() pintConcurrencyId = .ConcurrencyData() End Select End With End Sub
Notice that this method retrieves the data from the DBRecordset object. It retrieves each column in the order specified in the SELECT and uses the appropriate method of the DBRecordset object to retrieve the data type. Be sure to always retrieve the columns in the exact order you specified them in the SELECT statement. Doing this ensures that you can choose any data access method you want. The reason for this is the ODBC API must retrieve columns in the exact order in which you specified them in the SELECT statement.
This method will create a result set based on the primary key filter. You must be sure to fill in the primary key property of your object prior to calling the Find() method. Find simply sets the SelectFilter property to “PRIMARYKEY” prior to calling the FindByFilter() method:
Public Function Find() As Boolean Dim strOldFilter As String ' Set Filter For Retrieving One Row strOldFilter = pstrFilterName pstrFilterName = "PRIMARYKEY" Find = FindByFilter() ' Reset The Filter pstrFilterName = strOldFilter End Function
Note Find will always return only one row.
This method is called whenever you want to retrieve a result set based on a FilterName and some property or properties. The idea is if you want to find all dogs by a certain breed, you would fill in the BreedID property, then fill in the FilterName property with a string that defines a WHERE clause in the FilterGet() method:
Public Function FindByFilter() As Boolean ' Build Recordset & Retrieve Data If OpenRecordset() Then FindByFilter = True Else Call CloseRecordset FindByFilter = False End If End Function
Of course you could always just call the OpenRecordset() method.
Business rules must be enforced for all of your data. For example, in our Dog Class you should create a rule that no dog record can be saved unless there is a Dog’s Name filled in, and if a birth data is filled in, that it is a valid date. All of these business rules should go into a method called Check(). Below is an example of a Check() method for the Dog Class:
Public Function Check() As Boolean Check = False If Trim$(pstrDogName) = "" Then pstrInfoMsg = "Dog Name must be filled in." ElseIf Trim$(pstrBirthDate) <> "" Then If Not IsDate(pstrBirthDate) Then pstrInfoMsg = "Birth Date is an invalid date" Else Check = True End If Else Check = True End If End Function
The Check method is called from both the AddNew() and Replace() methods in our Dog Class. This ensures that the rules are always checked prior to saving the data to the base table. Notice that no message boxes are displayed from this routine. It simply returns a True or False value. If anything is in error, the InfoMsg property is filled in with an error message that the user can print out using their client software.
The AddNew method is responsible for inserting a new row into the base table. You will need to decide if you will be using an Identity or AutoNumber type of field for the primary key or not. If you are, then you do not need to include the primary key in the INSERT. If you are not, then you will need to write a routine to generate a unique ID for each table.
Public Function AddNew() As Boolean Dim strSQL As String AddNew = False Screen.MousePointer = vbHourglass ' Check Business Rules and Duplicate Values First If Check() Then strSQL = "INSERT INTO tblDogs (" strSQL = strSQL & "szDog_nm, " strSQL = strSQL & "lBreed_id, " strSQL = strSQL & "lBreeder_id, " strSQL = strSQL & "szColor_nm, " strSQL = strSQL & "sBark_type, " strSQL = strSQL & "cCost_amt, " strSQL = strSQL & "cPrice_amt, " strSQL = strSQL & "dtBirth_dt, " strSQL = strSQL & "sSex_nm, " strSQL = strSQL & "sLastUpdate_id, " strSQL = strSQL & "dtLastUpdate_dt, " strSQL = strSQL & "iConcurrency_id " strSQL = strSQL & ") VALUES (" strSQL = strSQL & Str2Field(pstrDogName) & ", " strSQL = strSQL & plngBreedId & ", " strSQL = strSQL & plngBreederId & ", " strSQL = strSQL & Str2Field(pstrColorName) & ", " strSQL = strSQL & Str2Field(pstrBarkType) & ", " strSQL = strSQL & pcurCostAmount & ", " strSQL = strSQL & pcurPriceAmount & ", " strSQL = strSQL & Date2Field(pstrBirthDate) & ", " strSQL = strSQL & Str2Field(pstrSex) & ", " strSQL = strSQL & Str2Field(poConnect.LoginID) & ", " strSQL = strSQL & Date2Field(Date) & ", 1) " ' Submit the SQL Statement If poConnect.Execute(strSQL) Then If poConnect.RecordsAffected = 1 Then AddNew = True Else AddNew = False End If Else pstrInfoMsg = poConnect.InfoMsg AddNew = False End If End If Screen.MousePointer = vbDefault End Function
The INSERT statement is submitted to the database using the connection object’s Execute() method. After this routine returns a True value, you can check the RecordsAffected property to determine if one row was added. If the Execute() method fails, you can retrieve any error information from the connection object.
The Replace method is very similar to the AddNew() method. It will build an UPDATE statement using the data in the properties of the class, then submit that statement to the database for processing:
Public Function Replace() As Boolean Dim strSQL As String Screen.MousePointer = vbHourglass If Check() Then strSQL = "UPDATE tblDogs SET " strSQL = strSQL & "szDog_nm = " & Str2Field(pstrDogName) & ", " strSQL = strSQL & "lBreed_id = " & plngBreedId & ", " strSQL = strSQL & "lBreeder_id = " & plngBreederId & ", " strSQL = strSQL & "szColor_nm = " & Str2Field(pstrColorName) & ", " strSQL = strSQL & "sBark_type = " & Str2Field(pstrBarkType) & ", " strSQL = strSQL & "cCost_amt = " & pcurCostAmount & ", " strSQL = strSQL & "cPrice_amt = " & pcurPriceAmount & ", " strSQL = strSQL & "dtBirth_dt = " & Date2Field(pstrBirthDate) & ", " strSQL = strSQL & "sSex_nm = " & Str2Field(pstrSex) & ", " strSQL = strSQL & "sLastUpdate_id = " & _ Str2Field(poConnect.LoginID) & ", " strSQL = strSQL & "dtLastUpdate_dt = " & Date2Field(Date) & ", " If pintConcurrencyId = -1 Then strSQL = strSQL & "iConcurrency_id = 1 " Else strSQL = strSQL & "iConcurrency_id = iConcurrency_id + 1 " End If strSQL = strSQL & " WHERE " strSQL = strSQL & "lDog_id = " & plngDogId & " " If pintConcurrencyId = -1 Then strSQL = strSQL & " AND iConcurrency_id = Null" Else strSQL = strSQL & " AND iConcurrency_id = " & pintConcurrencyId End If ' Submit the SQL Statement If poConnect.Execute(strSQL) Then If poConnect.RecordsAffected = 1 Then Replace = True If pintConcurrencyId = -1 Then pintConcurrencyId = 1 Else pintConcurrencyId = pintConcurrencyId + 1 End If Else Replace = False pstrInfoMsg = poConnect.InfoMsg & _ vbCrLf & "Could Not Update This Table: tblDogs " End If Else Replace = False End If End If Screen.MousePointer = vbDefault End Function
The UPDATE statement is submitted using the Execute() method of the connection object. You then check the RecordsAffected property to see if one row was updated. If it was, you know that the update was successful. If not, then you know it was not, and you can return error information.
When performing updates on data, you must be careful not to overwrite other people’s changes. Take the example where two people read the same record at the same time. If User 1 replaces the information first, User 2 still has the old data in their memory. If User 2 updates that information, the changes from User 1 will be lost.
To stop this from happening, you must have some mechanism to determine if the data has changed prior to performing an update. There are several techniques to make this happen. First you could reread the data from the table and compare field by field to an old copy of the original data. If any one field has changed, you know someone else has updated the row.
Another method is to use a TimeStamp. This is a special type of field that is updated by the database server everytime, and UPDATE is performed on a row. Unfortunately, not all database servers support this, and ISAM file systems such as Microsoft Access or dBase do not have this capability at all.
Knowing this, you need to devise some method of keeping track of when something changes.
A method that has worked very succssfully at our shop is to add one column to every table called iConcurrency_id. This is an Integer type that starts at the value 1 and will be incremented every time you perform an UPDATE. Unfortunately, you must take care of updating this value yourself. Look at the Replace() method you wrote earlier. In the UPDATE statement, you wrote the following:
… If pintConcurrencyId = -1 Then strSQL = strSQL & "iConcurrency_id = 1 " Else strSQL = strSQL & "iConcurrency_id = iConcurrency_id + 1 " End If strSQL = strSQL & " WHERE " strSQL = strSQL & "lDog_id = " & plngDogId & " " If pintConcurrencyId = -1 Then strSQL = strSQL & " AND iConcurrency_id = Null" Else strSQL = strSQL & " AND iConcurrency_id = " & pintConcurrencyId End If
First you check to see if the concurrency id is equal to –1. This means that it was a Null. If the value is Null, you want to set the concurrency id to 1 Otherwise, you increment the concurrency id by one. Now take the scenario presented above. If User 1 and User 2 both read the same row at the same time, they both read the same value for the concurrency id.
Say the value was a 1, then User 1 updates the row. The concurrency id is now equal to 2. User 2 still has the value of a 1 in their memory. When they attempt the update, the WHERE clause says to to check for where the concurrency id is equal to the value in User 2’s private variable pintConcurrencyId. This value is a 1. This means that the UPDATE statement will not find any rows, so the update will not take place. User 2 will receive 0 rows in the RecordsAffected property, which will signal that another user has updated the data before them. User 2 should then refresh their data to get User 1’s updated data.
The Delete() method will delete a row from the base table based on the primary key. Only one row will be deleted from the table. You need to fill in the primary key property with the value of the row you want to delete:
Public Function Delete() As Boolean Dim strOldFilter As String Screen.MousePointer = vbHourglass ' Set Filter Name strOldFilter = pstrFilterName pstrFilterName = "PRIMARYKEY" Delete = DeleteByFilter() If poConnect.RecordsAffected = 1 Then Delete = True Else Delete = False End If ' Reset The Filter pstrFilterName = strOldFilter Screen.MousePointer = vbDefault End Function
You can see that this method simply sets a filter to “PRIMARYKEY,” then calls the DeleteByFilter() method. It then checks the connection object’s RecordsAffected property to see if a 1 is returned. If a 1 is returned, then you know the row was deleted.
This method is called whenever you want to delete a set of rows based on a FilterName and some property or properties. The idea is if you want to delete all dogs by a certain breed, you would fill in the BreedID property, then fill in the FilterName property with a string that defines a WHERE clause in the FilterGet() method:
Public Function DeleteByFilter() As Boolean Dim strSQL As String Screen.MousePointer = vbHourglass ' Setup Delete SQL strSQL = "DELETE FROM tblDogs " ' Get The WHERE Clause for the DELETE statement ' Using the Existing Filter strSQL = strSQL & FilterGet() ' Submit the SQL Statement If poConnect.Execute(strSQL) Then DeleteByFilter = True Else pstrInfoMsg = poConnect.InfoMsg DeleteByFilter = False End If Screen.MousePointer = vbDefault End Function
When you are finished with the object, you should always close the recordset that was generated. The CloseRecordset method is used to close the recordset:
Public Sub CloseRecordset() If pboolRSActive Then poRS.Close pboolRSActive = False End If End Sub
In fact, this method is called from the Class_Terminate() event. This handles the case where the object goes out of scope and the CloseRecordset method was not called:
Private Sub Class_Terminate() Call CloseRecordset End Sub
This next section will present Database Connection Classes and Recordset Classes for different data access methods. The idea is to build a set of classes using different data access methods that you can plug into your application. This means you can swap your data access method at any time and not have to rewrite your application.
You are already familiar with the Database Connection Class for Jet. The connection class for ODBCDirect is very similar to this. Let’s look at where the changes have been made in this class so that you can get an idea of how to create additional Database Connection Classes as you need them.
The name of this database class is STILL DBConnect. You never want to change the name of the Class or any of the public interface. You can modify the private interface all you want as long as the functionality remains the same.
There is just one change to make to the private variables for the ODBCDirect Connection Class. Instead of a Database object, you need to use a Connection object:
Private poConn As Connection
There is only one public property that needs to change to implement ODBCDirect. First is the Property Get procedure for the DBObject. This property returns a reference to the connection object. In the case of Jet, it was a Database object. For ODBCDirect, it is a Connection object. Below is the code for the DBObject() property:
Property Get DBObject() As Connection Set DBObject = poConn End Property
The major changes for converting to a new database access method will occur in the public methods you created for this class. Specifically, the DataInit and DataOpen methods will need to be rewritten for ODBCDirect. Below are the samples for each one of these methods.
The DataInit() method is similar to the Jet engine version except the last parameter passed to the CreateWorkspace() method is to use ODBCDirect instead of Jet.
Public Function DataInit() As Integer On Error GoTo DataInit_EH If pws Is Nothing Then Set pws = _ DBEngine.CreateWorkspace("MultiTierODBCDirect", _ "sa", "", _ dbUseODBC) End If DataInit = True Exit Function DataInit_EH: pboolValidLogon = False pstrInfoMsg = Err.Description plngInfoCode = Err.Number DataInit = False Exit Function End Function
In the DataOpen() method, you will need to open a Connection object instead of a Database object as in Jet:
Public Function DataOpen() As Integer Dim strOldMsg As String On Error GoTo DataOpen_EH ' Display Status Message strOldMsg = StatusProcess("Opening Database...") DataOpen = True ' Open the Connection Object strConnect = "ODBC" & Me.ConnectString Set poConn = _ pws.OpenConnection("", _ dbDriverNoPrompt, _ False, _ strConnect) pboolOpen = True Call StatusRestore(strOldMsg) DataOpen = True pboolValidLogon = True Exit Function DataOpen_EH: DataOpen = False pstrInfoMsg = Err.Description plngInfoCode = Err.Number pboolValidLogon = False Exit Function End Function
The Recordset Class for ODBCDirect is exactly the same as the Jet engine. The only difference is in the types of Recordset objects you can create. You are a little more limited on the range of Recordset objects you can create.
Have you ever wanted an easy way to use just a portion of your application from a Microsoft Office application? Or maybe you needed to use some of it from a Microsoft C++ application. By creating an ActiveXÔ code component (formerly OLE Server) as a component, you are able to expose objects from your server to another application. The best part is you can use this server as just a normal application also.
These code components are a core part of creating a multi-tier architecture. Learning to create code components that are exposed in a server is actually quite simple. In this chapter, you will learn how to create servers that can be called from many different applications.
Probably the most exciting technology added to Visual Basic is the ability to create ActiveX code components. By building class modules and exposing properties and methods, you can create an application that has the ability to be called from any client application. You can create both in-process (DLLs) and out-of-process (EXE) code components. Using this technology, you can easily build a multi-tier client/server architecture.
Another feature of the code component is the ability to run the code component on any workstation in your network. This means you can put your business rules and data access routines on one very powerful machine, and all of your users can simply use the CPU of this other machine to perform the validation and data manipulation routines.
Let’s now walk step by step through the creation of a simple code component. You will create a Dog object that has two properties; Bark and Name. You will then create one method for this Dog object called BarkTheDog. This method will simply return a string that tells the dog’s name and what type of bark the dog has.
Sample File Name: AutoDog.vbp
Option Explicit Private pstrName As String Private pstrBark As String
After creating the private variables, you now need to create a couple of Property Get procedures so that you can read the data in each of these private variables:
Property Get Name() As String Name = pstrName End Property Property Get Bark() As String Bark = pstrBark End Property
To keep things simple, you should just default the private variables to some values. Enter the following code in the Class_Initialize event:
Private Sub Class_Initialize() pstrName = "Rusty" pstrBark = "Howl" End Sub
When the object is instantiated, the above code will fill in the values for this object.
Let’s create a method that will return the Dog’s name and bark. You will create a string that can be displayed in any front end:
Public Function BarkTheDog() As String Dim strMsg As String strMsg = "My Name is " & pstrName & vbCrLf strMsg = strMsg & " and I can " & pstrBark BarkTheDog = strMsg End Function
Now that all the pieces are filled in, you should fill in some project attributes so that you can identify this server by a meaningful name and a description:
Your screen should look something like the following:
When you are finished, click the OK button to save the changes. At this point you should save your project.
Now run this project. The application will appear to start and will be in run mode, but will not do anything.
To test this code component, follow the steps below:
Private Sub Command1_Click() Dim oDog As New Dog MsgBox oDog.BarkTheDog End Sub
Of course, this instance of Visual Basic does not know what a Dog object is, so you need to tell it where to find the server that has this type of object. Follow the steps below to specify the reference to the server:
Congratulations! You have created your first code component in Visual Basic.
Let’s now test our code component from Microsoft Excel. To accomplish this, follow the steps below:
Sub Test() Dim oDog As New Dog MsgBox oDog.BarkTheDog End Sub
There are actually two methods for calling a code component. The above method uses a technique called early binding. The code below will use a technique called late binding:
Sub Test() Dim oDog As Object Set oDog = CreateObject("AutoDog.Dog") MsgBox oDog.BarkTheDog End Sub
Notice how you just declared the oDog variable to be of the generic type Object. You then must use the CreateObject() function and pass as a paramter the name of the server and the name of the Class inside that server from which you want to create an object.
In the examples used in the previous section, we used two different kinds of binding techniques; early and late. Early binding means you have explicitly declared a code component name and a specific object in that code component in a Dim statement. For example:
Dim oDog As New AutoDog.Dog
Since you have explicitly stated the name of the server and the object within that server you are using early binding.
With late binding, you are waiting until run time to determine the object that will be loaded, and what server will be used. To use late binding, you must use the CreateObject() method.
Dim oDog As Object Set oDog = CreateObject("AutoDog.Dog")
The CreateObject() method will access the system registry at run time to find out which code component to run.
There are different reasons why you may want to use late binding versus early binding or vice versa. Early binding is much faster, but this also means that if you change your code component and you change something in this version, it could cause your client application to fail.
Late binding is slower, but if you change the version of your code component, your application may still continue to run, as long as the properties and methods are still compatible.
To register a code component after making an .EXE file, locate the file in File Manager, and double-click on the .EXE file. Visual Basic adds start-up code to any .EXE you make that has objects in it with the Public property set to true. This code tells Visual Basic to create registry entries for all exposed objects. Double-clicking on the code component does not start the code component.
Code components are only started at the request of another application and unloaded when all references to them are destroyed. Entries for your code components are contained under the HKEY_CLASSES_ROOT hive. The registered objects will all have a prefix of the Project Name you entered under the Project, Properties.., General tab.
To unregister a code component from the registry, run the .EXE file with the /U command line option.
Active code components may be used in two ways: either through Automation or by embedding an object in a Visual Basic form and using Visual Basic as the Active Container. Using Visual Basic, you can create code components that can be controlled through Automation.
So the question becomes, what kind of code component would you build using Visual Basic that can be useful to other programs? One of the best examples is a multi-tier client/server application. This type of architecture is based on having your user interface as one .EXE, all of the business rules and data access routines as a separate .EXE or .DLL (a code component), and the database engine as the third level. Other uses of code components are specialized applications like Microsoft Excel, or a drawing package like Visio.
Master Note: You must have Visual Basic 5.0 Professional Edition or Enterprise Edition to run this sample.
BARK server
Let’s now take the BARK application that you created earlier and separate the data access classes from the user interface. The idea is you will create two separate project files that each have different pieces of the complete BARK application in it. One project will have the CLS files and some .BAS files (the server), while the other will have the FRM files and some .BAS files, but no CLS files (the client).
Once you have created these two projects, you need to create an EXE out of the Server, then close that instance of Visual Basic. Next you load the client and, using the early binding technique shown earlier, set a reference to the Dog server.
Sample project files: DogServ.vbp and DogClient.vbp
Steps to run the code component:
The reason the objects from the data access classes are available is because of the reference that was set to the “Server for BARK Application.” If you stop the other instance of Visual Basic from running, then return to this instance, you should not see this reference anymore. What happens is that Visual Basic temporarily registers the code component so that other applications can see it. When you end the program, Visual Basic cleans up the registry.
Using the code component from Microsoft Excel
Now that you have made a real code component out of the BARK application, you can use this code component from any application that supports Automation. Let’s call our Dog object from Microsoft Excel and create a list of dogs in the spreadsheet.
Here is the VBA code you will write in Microsoft Excel:
Private Sub cmdLoad_Click() Dim oApp As New DogServer.Application Dim oDog As New DogServer.Dog Dim boolPerform As Boolean Dim intRow As Integer Call oApp.OpenDatabase Range("A4:B100").Select Selection.ClearContents Range("A4").Select oDog.SortName = "DOGNAME" boolPerform = oDog.OpenRecordset If boolPerform Then intRow = 4 Do While boolPerform Worksheets("DogList").Cells(intRow, 1) = oDog.DogName Worksheets("DogList").Cells(intRow, 2) = _ oDog.BirthDate intRow = intRow + 1 boolPerform = oDog.MoveNext Loop oDog.CloseRecordset Else Beep MsgBox oDog.InfoMsg End If End Sub
The only difference between the code shown above and the code from the Visual Basic sample is that you specify the name of the server as well as the object within the object. Also, you specify the cells you want to put the data into. Other than that, the code is nearly identical to the code that you write in Visual Basic.
When you make an .EXE file out of a Visual Basic project that has a public class, you are creating an out-of-process server. This means that your application .EXE will run in one process space, and the code component you call will run in another process space. You will be performing cross-process communication via Active Automation.
An In-Process Server (IPS) is an Active DLL that can be created in Visual Basic. These Active DLLs have to be created with the 32-bit version of Visual Basic and can only be run in a 32-bit environment. These DLLs share the same process space as the .EXE file that uses them. This makes them much faster than the out-of-process code components. Using an in-process server is exactly the same as an out-of-process code component.
Rules of In-Process servers
These require particular coding conventions, and there are a lot of rules to programming them. You should learn some of the these rules before coding your servers. Below are some of the rules for an IPS:
This is just a partial list of the rules associated with an IPS. Search the Help for more information on In-Process Servers.
Benefits of In-Process servers
The major benefit of an in-process server as opposed to an out-of-process server is SPEED! Since this runs as a DLL in the same process space, there is no cross-process communication that has to take place. This can significantly speed up the time it takes to invoke methods and set/read properties.
In this chapter, you learned to put together a template for all your data access classes. By putting together your data access classes to use the DBRecordset object, you can change your data access method within the DBRecordset object, and you don’t need to change the Data Access Class. This means as new ways of accessing data are introduced, you don’t need to rewrite your whole application, only a couple of classes.
A couple of other benefits are that you encapsulate business rules in one place. You have a common interface to a table. There is no need to write SQL code all over your application; you can put it all into one place.
In this chapter, you learned to create database connection classes for some other database access methods. By abstracting connections, your base classes that deal with tables can remain the same, and you only need to add the new connection class for the database access method you want to use.
You also learned to create Recordset classes for additional database methods. Once again, this helps you abstract dealing with result sets of data so that your base classes do not need to change.
You also learned how to create a code component out of the application built in the previous chapter.
© 1997 Microsoft Corporation. All rights reserved.
The information contained in this document represents the current view of Microsoft Corporation on the issues discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information presented after the date of publication.
This document is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS OR IMPLIED, IN THIS DOCUMENT.
Microsoft and Visual Basic are registered trademarks and ActiveX is a trademark of Microsoft Corporation.
Other product and company names listed herein may be the trademarks of their respective owners.