patternMinor
Retrieving database values through properties
Viewed 0 times
propertiesdatabasethroughvaluesretrieving
Problem
I have a
Then I can call it like this:
The same approach could easily apply to an objectFrame/subform scenario.
So my question is, is this approach I've taken considered to be a good practice and does it add a considerable amount of overhead to the application?
Settings table which I make frequent DlookUp calls to retrieve values. So I thought instead of having to write Nz(DLookup("FieldName", "TableName", "Condition"), vbNullString) everytime I needed the value, why not wrap it in a property in a standard module.Public Property Get ActiveKey() As String
ActiveKey = Nz(DLookup("[_ActiveKey]", "WmSettings"), vbNullString)
End Property
Public Property Let ActiveKey(ByVal pValue As String)
CurrentDb.Execute StringFormat("UPDATE WmSettings SET [_ActiveKey]='{0}';", pValue), dbFailOnError
End PropertyThen I can call it like this:
key_ = Settings.ActiveKey 'I tend to write the module name for clarity
Settings.ActiveKey = "FAD22L"The same approach could easily apply to an objectFrame/subform scenario.
Public Property Get PageFrame() As Object
Set PageFrame = Forms.frmMain.SubForm.Form.InnerSubForm
End Property
Public Property Get Page() As Form
Set Page = Forms.frmMain.SubForm.Form.InnerSubForm.Form
End PropertySo my question is, is this approach I've taken considered to be a good practice and does it add a considerable amount of overhead to the application?
Solution
Properties in Standard Modules
Standard modules can have Properties, but as @Rubberduck pointed out, they're not necessarily intuitive (although they do force you to qualify a call with the module name, so there's that as a redeeming quality). You could write public methods instead, but you could also create a class, and if you wanted to avoid the need to
Right now, your approach looks up the value from the underlying table every time, and writes it to the table every time. That's time consuming disk access and page locking. It might be better to load the settings on demand, use/change the settings, and then save them on exit.
Variable Names
SQL Injection
Kudos for using the
At the very least, sanitize your inputs. Better still, parameterize your queries and leave a sign up saying that Johnny DropTables isn't welcome.
LateBound
You're explicitly returning an
If you know the return-type will always be an instance of
If the subform is set dynamically, and you only know the return type will be a Form, then return that base type:
Clarifying the Form type
Access has built-in
Be careful with properties matching built-in control names
Accessing the
The Forms object extends the names of open forms at run-time. That means you're using late-bound code and you're not getting any Intellisense. Worse still, if the form isn't open, or it's in the wrong state, the call will fail. At the very least, you should have some error handling.
Using the default instance of the form
Access creates a default predeclared instance of a form (much like VBA creates a default predeclared instance of a UserForm), but Access only does this if the form is open. However, it is also possible to create instances of the Access form (and, I'd argue, is the preferable way to deal with Forms and UserForms).
Your code is intimately tied to the default instance, and should perhaps be made capable of handling any instance of the form. See this example to see how a form with a default caption of "foo" is handled by VBA:
The following example attempts to use the CurrentDb function to return a
pointer to the database that is currently open in Microsoft Access. Because
the code does not assign that database to an object variable, the pointer
returned by the CurrentDb function is temporary and becomes invalid after
the TableDef object is set. Consequently, any later references in your code
to the TableDef object variable will result in an error.
That is, usage of
```
D
Standard modules can have Properties, but as @Rubberduck pointed out, they're not necessarily intuitive (although they do force you to qualify a call with the module name, so there's that as a redeeming quality). You could write public methods instead, but you could also create a class, and if you wanted to avoid the need to
New it up, you could give that class a Predeclared attribute. A class would better reflect the stateful nature of the settings, and would give you the flexibility of abstracting the persistence of settings from the retrieval and usage of settings.Right now, your approach looks up the value from the underlying table every time, and writes it to the table every time. That's time consuming disk access and page locking. It might be better to load the settings on demand, use/change the settings, and then save them on exit.
Variable Names
pValue is presumably short for parameterValue. Why not use the full name, or better still, something like keyName? pValue could be confused for the statistical term P-Value, or a user might infer that the p is Hungarian notation, but guess at what type that notation represents.SQL Injection
Kudos for using the
StringFormat (presumably from @Mat'sMug's CR question), but you're building a SQL statement on the fly, and a malicious user, or inadvertent user could cause an error, or worse, by providing a parameter with a single-quote in the pValue. What happens if the user supplied "', AdminMode = TRUE, AdminName = '"?At the very least, sanitize your inputs. Better still, parameterize your queries and leave a sign up saying that Johnny DropTables isn't welcome.
LateBound
PageFrame propertyYou're explicitly returning an
Object, which forces usage of the property to be late-bound. You probably know the type of the return value, why not use it?If you know the return-type will always be an instance of
InnserSubForm, then return that type:Public Property Get PageFrame() As Form_InnerSubForm
Set PageFrame = Forms.frmMain.SubForm.Form.InnerSubForm
End PropertyIf the subform is set dynamically, and you only know the return type will be a Form, then return that base type:
Public Property Get PageFrame() As Access.Form
Set PageFrame = Forms.frmMain.SubForm.Form.InnerSubForm
End PropertyClarifying the Form type
Access has built-in
Form objects, but also allows the use of VBA UserForms. It might be helpful to qualify the type as Access.Form:Public Property Get Page() As Access.FormBe careful with properties matching built-in control names
Page is the name of a built-in type, but you're using it to return a Form. That might be confusing for users of the property.Accessing the
Forms objectThe Forms object extends the names of open forms at run-time. That means you're using late-bound code and you're not getting any Intellisense. Worse still, if the form isn't open, or it's in the wrong state, the call will fail. At the very least, you should have some error handling.
Using the default instance of the form
Access creates a default predeclared instance of a form (much like VBA creates a default predeclared instance of a UserForm), but Access only does this if the form is open. However, it is also possible to create instances of the Access form (and, I'd argue, is the preferable way to deal with Forms and UserForms).
Your code is intimately tied to the default instance, and should perhaps be made capable of handling any instance of the form. See this example to see how a form with a default caption of "foo" is handled by VBA:
Dim frm As Form_frmMain
Set frm = New Form_frmMain
frm.Caption = "Bar"
'Print the current instance's caption - Bar
Debug.Print frm.Caption
'Print the early-bound default instance's caption - Foo
Debug.Print Form_frmMain.Caption
'Print the late-bound default instance's caption - Foo
Debug.Print Forms.frmmain.CaptionCurrentDb is a function used like an objectCurrentDB is a function that returns a pointer to a Database. It's a member of the Application class, but note this text from an old KB article:The following example attempts to use the CurrentDb function to return a
pointer to the database that is currently open in Microsoft Access. Because
the code does not assign that database to an object variable, the pointer
returned by the CurrentDb function is temporary and becomes invalid after
the TableDef object is set. Consequently, any later references in your code
to the TableDef object variable will result in an error.
That is, usage of
Database members against CurrentDB, like CurrentDB.Execute or, more correctly, CurrentDB().Execute can be unreliable and result in error Object Invalid or Not Set. You'll have more reliable code if you first assign the result of CurrentDB() to a local variable, and then access the Execute method from that variable.```
D
Code Snippets
Public Property Get PageFrame() As Form_InnerSubForm
Set PageFrame = Forms.frmMain.SubForm.Form.InnerSubForm
End PropertyPublic Property Get PageFrame() As Access.Form
Set PageFrame = Forms.frmMain.SubForm.Form.InnerSubForm
End PropertyPublic Property Get Page() As Access.FormDim frm As Form_frmMain
Set frm = New Form_frmMain
frm.Caption = "Bar"
'Print the current instance's caption - Bar
Debug.Print frm.Caption
'Print the early-bound default instance's caption - Foo
Debug.Print Form_frmMain.Caption
'Print the late-bound default instance's caption - Foo
Debug.Print Forms.frmmain.CaptionDim db As Database
Set db = Application.CurrentDb()
db.Execute StringFormat("UPDATE WmSettings SET [_ActiveKey]='{0}';", pValue), dbFailOnErrorContext
StackExchange Code Review Q#162098, answer score: 5
Revisions (0)
No revisions yet.