[A picture of private offices at Fog Creek Software] Alert! This ancient trifle retrieved from the Joel on Software archive is well-past its expiration date. Proceed with care.

Joel on Software

CityDesk Entity Classes

by Joel Spolsky
Friday, January 03, 2003

Comment: This article is Fog Creek Internal documentation, likely to be of interest only to hard core VB 6.0 geeks working with databases through DAO. Rather than use the DAO classes directly and scattering SQL syntax throughout our code, we try to isolate database access by using a thin layer which provides simple objects that map to underlying data. These objects are called "entities."

About 99% of what we do with databases consists of:

  • select one record
  • enumerate records, possibly matching a query
  • change a record
  • add a new record

The goal of entity classes is to make these things easy and consistent.

To isolate and simplify all access to the CityDesk database, all database access code goes through a set of classes called entity classes. These are VB user-defined classes, which are easily identified because the class names always start with the capital letter E followed by the name of the table. For example, you would use an entity class called EArticle to access the table tblArticle. There does not have to be an underlying physical table for each entity class. If you need to do a sophisticated query that joins multiple tables, this can be done inside an entity which still starts with E and has some sensible name but which does not correspond to a physical table.

Golden Rule: Never let entity classes cause you to do something inefficient with the database.

Comment: A lot of times people using the entity classes forget about what's involved in the underlying query, and end up writing code that's ridiculously inefficient. For example, updating a table by looping through it and committing each change, rather than using a SQL UPDATE statement. This is explicitly forbidden.

Each entity class has public members for each of the columns in the table with the exact same names. For example, for tblArticle, EArticle has ixArticle, ixSet, ixLanguage, etc. These are not implement as property get/lets, that just makes the code ugly; they are implemented as plain members. There is ONE EXCEPTION: any large or blob type fields are implemented as property get/lets which defer the actual database access until you access that particular field.

Comment: CityDesk has a lot of tables containing a large blob field, for example, the table of articles. If you're enumerating the articles in order to list them, you don't want to spend any cycles extracting the blobs.

When you create a new entity object, name it with the same name as the table in lower case, for example:

Dim article As EArticle : Set article = New EArticle

Creating a new entity object initializes all the fields to something sensible and initializes the primary key ix field (e.g. ixArticle) to -1.

Entity objects usually have these methods:


Loads the object from a single record, where the primary key is ix.

Dim article As EArticle : Set article = New EArticle
article.Load 1 ' load article where ixArticle = 1
Debug.Print article.ixLanguage


Commits any changes. If the primary key is still -1, this does an INSERT and always returns the just inserted primary key as a return value. If the primary key is not -1, this does an Update...Where and still returns the primary key.

' Add new article:
Dim article As EArticle : Set article = New EArticle
article.ixLanguage = 1
' set all the other fields
Dim ixArticleNew: ixArticleNew = article.Save

' Now edit it:
article.Load ixArticleNew
article.ixLanguage = 2

Comment: We always use autonumber/identity fields for primary keys. One of the most maddening things about SQL is the numerous, inconsistent and unreliable ways of getting the identity field you just inserted.


Used for enumerating through the records. The plain version of List starts enumerating them all. If there are zero matching records, that is, if EOF is true immediately, List returns False, in case you want to have different code for that case.

If you need to return any other kind of list, for example, all non-deleted, you use a variation of List like ListNonDeleted, which can take arguments.

Then .GetNext loads the first record and returns true if not EOF.

' List templates:
Dim template As ETemplate: Set template = New ETemplate
If template.List Then
    While template.GetNext
         Debug.Print template.Name
         ' No need to move next!
    Debug.Print "Nothing!"
End If

Comment: A common mistake in writing DAO code is forgetting the MoveNext at the bottom of your loop. This eliminates the need for it.


HyperDev is the developer playground for building full-stack web apps, fast.

Want to know more?

You’re reading Joel on Software, stuffed with years and years of completely raving mad articles about software development, managing software teams, designing user interfaces, running successful software companies, and rubber duckies.

About the author.

I’m Joel Spolsky, co-founder of Trello and Fog Creek Software, and CEO of Stack Overflow. More about me.

© 2000-2016 Joel Spolsky