Class Modules – Unbound Forms iii – CRUD

In this post we will be giving you the code you need to Create, Update and Delete records through a Class Module.

Encapsulation

One of the best reasons to use a class module is because of a concept known as encapsulation. Encapsulation means that all the code you need to generate an object is held within the class. In other words, a form that uses the clsFilms class will only need to reference the properties and methods of said class and not need to know anything else about clsFilms. The Film data is going to be retrieved and stored somewhere but the form will not need to know where; the form only knows about the public methods of the class and frankly that is all it needs to know.

Therefore, the code that takes care of creating, updating and deleting records in the Films table will reside in methods within the class. Let’s take a look at what extra code we need to CRUD up our class.

The Code

Private rs As ADODB.Recordset
Private cnn As ADODB.Connection
Private strSQL As String

''''''''''''''''''''''''''''''''''''''''''''
'''''Initilisation and kill code''''''''''''
''''''''''''''''''''''''''''''''''''''''''''
Private Sub Class_Initialize()
Set cnn = CurrentProject.Connection
Set rs = New ADODB.Recordset
End Sub

Private Sub Class_Terminate()
Call killRecordset
End Sub

Public Sub loadData(ID As Long)
'This sub procedure loads the data based upon the
'passed in ID value -1 means new record

If ID > 0 Then
    strSQL = "Select * From Films Where [ID]=" & ID
    Call loadRecordset(strSQL)
    Call setFields
Else
    m_ID = -1
    strSQL = "Select * From Films "
    Call loadRecordset(strSQL)
End If
End Sub

''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''Recordset code''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''

Private Sub loadRecordset(strSQL As String)
With rs
        .Open strSQL, cnn, adOpenKeyset, adLockOptimistic
        .MoveLast
        .MoveFirst
    End With
End Sub

Private Sub killRecordset()
If Not rs Is Nothing Then rs.Close
Set rs = Nothing
Set cnn = Nothing
End Sub

Public Sub setFields()
With rs
    m_ID = .Fields("ID")
    m_FilmName = .Fields("FilmName")
    m_YearOfRelease = .Fields("YearOfRelease")
    m_RottenTomato = .Fields("RottenTomatoes")
    m_Director = .Fields("DirectorID")
End With
End Sub

''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''Record Operations'''''''''''''
''''''''''''''''''''''''''''''''''''''''''''

Public Function SaveRecord() As Boolean
With rs
    If m_ID > 0 Then

        .Fields("FilmName") = m_FilmName
        .Fields("YearOfRelease") = m_YearOfRelease
        .Fields("RottenTomatoes") = m_RottenTomato
        .Fields("DirectorID") = m_Director
        .Update
    Else
        .AddNew
        .Fields("FilmName") = m_FilmName
        .Fields("YearOfRelease") = m_YearOfRelease
        .Fields("RottenTomatoes") = m_RottenTomato
        .Fields("DirectorID") = m_Director
        .Update
    End If
End With
End Function

Public Function UndoRecord() As Boolean
With rs
    m_ID = .Fields("ID")
    m_FilmName = .Fields("FilmName")
    m_YearOfRelease = .Fields("YearOfRelease")
    m_RottenTomato = .Fields("RottenTomatoes")
    m_Director = .Fields("DirectorID")
End With
End Function

Public Function DeleteRecord() As Boolean
Dim lngID As Long

lngID = m_ID
'We are going to use a simple query to delete the record
Call killRecordset

strSQL = "DELETE FROM Films WHERE [ID]=" & lngID
CurrentDb.Execute strSQL, dbFailOnError

End Function

OK, so you weren’t expecting quite as much code as that. Well, class module coding is quite verbose and involves a much higher initial time investment than standard coding but don’t leave us just yet! We promise that it will be worth every effort and will bring long term rewards.

The Explanation

Private rs As ADODB.Recordset
Private cnn As ADODB.Connection
Private strSQL As String

These are instance variables and belong to the class. They are equivalent to properties in every way except they don’t have any Get or Let properties methods attached to them and for this reason cannot be accessed from outside of the class. Therefore, they have not been prefixed with m_.

Private Sub Class_Initialize()
Set cnn = CurrentProject.Connection
Set rs = New ADODB.Recordset
End Sub

Private Sub Class_Terminate()
Call killRecordset
End Sub

Whenever you instantiate a class you have the option to add a sub-procedure that runs some code. In the above code snippet, we have a Class_Initialize method that instantiates a recordset (Set rs = New ADODB.Recordset). Because rs is an instance variable, as long as the class is instantiated, the rs will persist. What? In other words, the recordset will be available until the object created by the class is killed.

When the object created by the class is killed, we also have an option to run a sub-procedure. Class_Terminate will call another sub-procedure(KillRecordset) that will kill the recordset.

Note:  In this class module we will be using ADODB recordsets instead of DAO recordsets. You absolutely can use DAO but if you use ADODB, you only need change the connection string in order to connect with a different database system, such as SQL Server.

Public Sub loadData(ID As Long)
'This sub procedure loads the data based upon the
'passed in ID value. -1 means new record

If ID > 0 Then
    strSQL = "Select * From Films Where [ID]=" & ID
    Call loadRecordset(strSQL)
    Call setFields
Else
    m_ID = -1
    strSQL = "Select * From Films "
    Call loadRecordset(strSQL)
End If
End Sub

We know that rs is an ADODB.Recordset and that it is an object that is designed to hold a dataset but we haven’t told it which dataset to hold.

Public Sub loadData(ID As Long) 

The name of the sub-procedure is loadData and it takes a single argument (ID as long). We know the ID of the record we want to see and pass it into the class.

If ID > 0 Then

If we are adding a new record, the ID will be -1.

strSQL = "Select * From Films Where [ID]=" & ID 

We create a SQL Statement that we use as the basis for the recordset.

Call loadRecordset(strSQL) 

In order to load the recordset we call the loadRecordset method (discussed below).

Call setFields 

We call another function to set the properties of the class equal to the values of the recordset.

m_ID = -1
  strSQL = "Select * From Films "
  Call loadRecordset(strSQL) 

We use this code for when we are creating a new record. As we don’t have a record to use in Films, we open the Film table awaiting an insert.

Private Sub loadRecordset(strSQL As String)
    With rs
        .Open strSQL, cnn, adOpenKeyset, adLockOptimistic
        .MoveLast
        .MoveFirst
    End With
End Sub

This method loads a recordset based upon the passed in strSQL argument.

Private Sub loadRecordset(strSQL As String)

The name of the method is loadRecordset and it takes a single argument (strSQL as String).

With rs...End With 

By using a with statement we only need to use the dot (.) operator to reference properties of the recordset object. (.open instead of rs.open).

.Open strSQL, cnn, adOpenKeyset, adLockOptimistic 

With this line of code we open a recordset based on the strSQL variable (it will be something like: SELECT * FROM Films). adOpenKeySet determines the cursor type. adLockOptimistic determines the locking type. To learn more about cursor and lock types view this page.

.moveLast
.moveFirst 

Not strictly necessary but always worth moving to the last record and back to the first record so that the recordcount property is populated.

Public Sub setFields()
With rs
    m_ID = .Fields("ID")
    m_FilmName = .Fields("FilmName")
    m_YearOfRelease = .Fields("YearOfRelease")
    m_RottenTomato = .Fields("RottenTomatoes")
    m_Director = .Fields("DirectorID")
End With
End Sub

This is a simple sub-procedure that takes the values of the fields in the active record in the ADODB recordset and adds them to the clsFilm properties. We have given this code its own method as it is called from different locations within the class.

Private Sub killRecordset()
If Not rs Is Nothing Then rs.Close
Set rs = Nothing
Set cnn = Nothing
End Sub

This method simply kills the recordset object. Again, we have put this code in its own method as it is called from multiple points in the class.

Public Function SaveRecord() As Boolean
With rs
    If m_ID > 0 Then

        .Fields("FilmName") = m_FilmName
        .Fields("YearOfRelease") = m_YearOfRelease
        .Fields("RottenTomatoes") = m_RottenTomato
        .Fields("DirectorID") = m_Director
        .Update
    Else
        .AddNew
        .Fields("FilmName") = m_FilmName
        .Fields("YearOfRelease") = m_YearOfRelease
        .Fields("RottenTomatoes") = m_RottenTomato
        .Fields("DirectorID") = m_Director
        .Update
    End If
End With
End Function

Now we get to the methods that really interest us. This is the reverse of the setFields method as SaveRecord takes the values of the clsFilms properties and either writes them to the database or inserts a new record (it evaluates the value of m_ID; -1 means that the record is new, any other value is the ID of the film).

The code we need to update to the table is fairly simple as we already have a recordset object open.

Public Function UndoRecord() As Boolean
With rs
    m_ID = .Fields("ID")
    m_FilmName = .Fields("FilmName")
    m_YearOfRelease = .Fields("YearOfRelease")
    m_RottenTomato = .Fields("RottenTomatoes")
    m_Director = .Fields("DirectorID")
End With
End Function

Something you will need is the ability to undo changes made to a form. Our concept of undoing is simple and in fact a little redundant. When a user uses an unbound form, none of the changes made to the form are committed to the database until the save button is clicked. So, to undo the changes all we need to do is read the original property values from the class module again (just like when the form loads). We are only including this code in the class to ensure that the properties of the class are directly lifted from the table.

Public Function DeleteRecord() As Boolean
Dim lngID As Long

lngID = m_ID
'We are going to use a simple query to delete the record
Call killRecordset

strSQL = "DELETE FROM Films WHERE [ID]=" & lngID
CurrentDb.Execute strSQL, dbFailOnError

End Function

In this method we are providing the ability to delete a record from the database. Although we have an open recordset, we don’t want to pass a delete command to it as it is possible to delete all the records in the recordset by accident! We only want to delete the active record but don’t want to take a risk with our data. For that reason, we are generating an SQL statement and executing it.

lngID = m_ID

We need to know which record to delete so we use the ID of the active record in the recordset.

Call killRecordset

We have a sub-procedure to kill the recordset and call it before doing anything else in this method.

strSQL = "DELETE FROM Films WHERE [ID]=" & lngID

This is a simple SQL Statement that will delete a specific record from the Films table (based on the ID).

CurrentDb.Execute strSQL, dbFailOnError

This line of code executes the query. If the query fails, an error will be generated.

Here is the database with the added code.

3 Comments

  1. Using Access 2013, I added references Microsoft ActiveX Data Objects 6.1 Library and Recordset 6.0 Library.

    • Code contains 2 copies of killRecordset. User should “kill” a copy to run code properly. Duplicate also in Films 4 downloadable database.

      • Thanks for the spot. I have updated the code and removed the extra KillRecordset. I have uploaded a corrected Films 04 database as well.

Leave a Reply to Robert AustinCancel Reply

Your email address will not be published. Required fields are marked *

Visit Us On TwitterVisit Us On FacebookVisit Us On Youtube