Storing recordset data

Posted on

Problem

I have managed to write some pretty terrible code in VBA and I know there has to be a better way. I have made all of these variables to store some data which I eventually reference in the application. I’m fairly certain I need to loop over the recordset or fields and store the variables that way but I can’t figure out how to do it in a way that actually simplifies this code. It’s torturing me because it seems like a painfully simple concept.

The columns of the table that it is gathering data from looks similar to this:

[LANGUAGE_ID]-[NEXT]-[PREVIOUS]-[RESUME] — and so on

sqlstmt = "SELECT *" & _
" FROM cf_Language" & _
" WHERE LANGUAGE_ID = " & parm_language & ";"

Set rst_t = dbs.OpenRecordset(sqlstmt, dbOpenSnapshot)

If (parm_language <> 1) Then
    parm_surveyname = UnicodeToAscii(parm_surveyname, True)
    aNext = UnicodeToAscii(rst_t![Next], True)
    aPrevious = UnicodeToAscii(rst_t![previous], True)
    aResume = UnicodeToAscii(rst_t![resume], True)
    aFinish = UnicodeToAscii(rst_t![finish], True)
    aSave = UnicodeToAscii(rst_t![Save], True)
    aLogIn = UnicodeToAscii(rst_t![log_in], True)
    aComplete = UnicodeToAscii(rst_t![complete], True)
    aHeader = UnicodeToAscii(rst_t![header], True)
    aThanks = UnicodeToAscii(rst_t![thanks], True)
    aSavedMsg = UnicodeToAscii(rst_t![saved_msg], True)
    aBlankCode = UnicodeToAscii(rst_t![blank_code], True)
    aInvalidCode = UnicodeToAscii(rst_t![invalid_code], True)
    aTimeOut = UnicodeToAscii(rst_t![timeout], True)
    aClosed = UnicodeToAscii(rst_t![closed], True)
    aAccessCode = UnicodeToAscii(rst_t![access_code], True)
    aConFirstTime = UnicodeToAscii(rst_t![con_first_time], True)
    aAnonFirstTime = UnicodeToAscii(rst_t![anon_first_time], True)
    aAnonResume = UnicodeToAscii(rst_t![anon_resume], True)
Else
    aNext = rst_t![Next]
    aPrevious = rst_t![previous]
    aResume = rst_t![resume]
    aFinish = rst_t![finish]
    aSave = rst_t![Save]
    aLogIn = rst_t![log_in]
    aComplete = rst_t![complete]
    aHeader = rst_t![header]
    aThanks = rst_t![thanks]
    aSavedMsg = rst_t![saved_msg]
    aBlankCode = rst_t![blank_code]
    aInvalidCode = rst_t![invalid_code]
    aTimeOut = rst_t![timeout]
    aClosed = rst_t![closed]
    aAccessCode = rst_t![access_code]
    aConFirstTime = rst_t![con_first_time]
    aAnonFirstTime = rst_t![anon_first_time]
    aAnonResume = rst_t![anon_resume]
End If

Solution

Key Points

  • I would not use local variables to hold recordset values, because you’d need one for every field of every returned row… which is just wrong.
  • As already pointed out, good variable names are readable variable names (which can be pronounced!).
  • You’re not showing it, but make sure you close that connection once you’re done.

“Entities”

Here’s an [perhaps less appealing] alternative to Dictionary. Since I learned about C# and started using Entity Framework, I like wrapping each record in an object – given a class called MyEntity (which does nothing more than exposing getters and setters for all properties):

Private Type ThisEntity
    SomeProperty As String
    AnotherProperty As String
End Type

Private this As ThisEntity
Option Explicit

Public Property Get SomeProperty() As String
    SomeProperty = this.SomeProperty
End Property

Public Property Let SomeProperty(value As String)
    this.SomeProperty = value
End Property

Public Property Get AnotherProperty() As String
    AnotherProperty = this.AnotherProperty
End Property

Public Property Let AnotherProperty(value As String)
    this.AnotherProperty = value
End Property

You define this object only once, and then you instantiate it as many times as you have records in your recordset. With a dictionary you get easier loading (each “entity” is a dictionary where you identify each field by their names to retrieve the corresponding value), but with this object you completely avoid referring to your properties using strings – the only hard-coded strings involved are the field names.

Reading the recordset

All you need to do is exactly as you said, loop through the recordset:

Dim entity As MyEntity
Dim entities As New Collection
While Not rs.EOF And Not rs.BOF 'BOF check if result has 0 rows

    Set entity = New MyEntity
    With entity
        .SomeProperty = rs.Fields("SomeField").Value 'or rs!SomeField
        .AnotherProperty = rs.Fields("AnotherField").Value 'or rs!AnotherField
    End With

    entities.Add entity ', entity.Id /PK property if you want to use a key here
    rs.MoveNext 'don't forget this, or your loop will be endless!
Wend

rs.Close
Set rs = Nothing
'at this point you have your entire recordset's content in the "entities" collection

About rs!FieldName notation (little “Gotcha!”)

For what it’s worth, I’ve recently fixed a bug in our codebase that was caused by the rs!FieldName notation in a specific circumstance: rs!FieldName was being added to a collection, and the recordset was left open. When I found that out, I added rs.Close and all of a sudden the collection, although non-empty, could no longer be read. This is because adding rs!FieldName to the collection (or, if you take the dictionary road) was actually adding a reference to the Field object, and the original programmer was left with the impression it worked fine because he could retrieve any entry in the collection and see the field’s value – when he was actually retrieving a Field object and through the default property was fetching the value from the recordset: that stopped working when I added code to close the recordset after all records were read.

That bug was fixed by explicitly adding rs.Fields(“FieldName”).Value to the collection instead.

From the help : “The default collection of a Recordset object is the Fields collection, and the default property of a Field object is the Value property. Use these defaults to simplify your code.”

I would either :

  • Loop over the fields and create a dictionary with name/value pairs
  • Make sure that rst_t is of type Snapshot and just keep using rst_t

Other than that:

  • Hungarian notation, just don’t do it
  • Disemvoweling ( sqlstmt ? ), just don’t do it either
  • Seems like you could simply always call UnicodeToAscii and cut half of the code

On dictionaries in VBa:
https://stackoverflow.com/questions/915317/does-vba-have-dictionary-structure
On Hungarian notation ( and why it is bad ):
http://en.wikipedia.org/wiki/Hungarian_notation

I could not find a good article on disemvoweling ( remove all vowels from the name and then some like ‘statement’ -> ‘sttmnt’ -> ‘stmt’ ), I was sure Coding Horror had one. Basically it just makes your code harder to read for other people, it is one more barrier to (mis)understand code.

Leave a Reply

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