Part of the elegance of using an ORM product is that you can
continue to use the domain (or call them entity) objects that you might design
before event deciding on using ORM. This allows you to keep your custom
objects and other steps towards a domain model and OOP principles intact while
at the same time simplifying your database access. See Figure 1 for a part of
the domain model used on this project.
In the above figure you can see part of the domain model.
Here, we have objects describing a Listing, Feedback, Owner, Event and an
interface for Event named IEventDetail.
The configuration file is what ties together the database
tables and fields with your objects as shown in Listing 1.
<?xml version="1.0" encoding="utf-8" ?>
<mappings version="4.2" defaultNamespace="">
<entity type="Owner" table="Owner"
keyMember="Id" keyType="Auto" sortOrder="LastName ASC" autoTrack="false">
<attribute member="Id" field="OwnerId" alias="Id" />
<attribute member="FirstName" field="FirstName" alias="FirstName" />
<attribute member="LastName" field="LastName" alias="LastName" />
<attribute member="Street" field="Street" alias="Street" />
<attribute member="Street2" field="Street2" alias="Street2" />
<attribute member="Number" field="Number" alias="Number" />
<attribute member="City" field="City" alias="City" />
<attribute member="State" field="State" alias="State" />
<attribute member="Postal" field="Postal" alias="Postal" />
<attribute member="Country" field="Country" alias="Country" />
<attribute member="Company" field="Company" alias="Company" />
<attribute member="Email" field="Email" alias="Email" />
<attribute member="UserName" field="UserName" alias="UserName" />
<attribute member="Salt" field="Salt" alias="Salt" />
<attribute member="PasswordEncrypted" field="Password" alias="Password" />
<attribute member="Role" field="Role" alias="Role" />
<relation relationship="OneToMany" member="Listings" field="OwnerId"
type="Listing" alias="Listings" lazyLoad="true" cascadeDelete="true" />
<relation relationship="OneToMany" member="Events" field="OwnerId"
type="Event" alias="Events" lazyLoad="true" cascadeDelete="false" />
The configuration file is in XML, so the hierarchy of data
is simple to read and just as easy to understand after you use it for a short
while. The entire file is wrapped in the mappings element and each
table-to-object relationship is defined in an entity element. The Listing 1
example above ties the object Owner to the table Owner. It sets the object
key, Id, to the tables' primary key via the keymember attribute. It then sets
the default sort order and a attribute named autoTrack which lets the ORM
framework know whether or not to watch this object and perform SQL changes on
Each property of the object is then mapped to a database
field via an attribute element and member attribute.
Notice the element relation. This provides foreign key
relationships to other tables. In this example the field OwnerId in the table Owner
is linked to the table Listing with a synonymous field name. From here we can
set the Listing object to be lazy loaded inside of the Owner object. So, to
summarize this, each object is mapped to a table in the configuration file.
Furthermore, a related object can be lazy loaded as a property in another
object via foreign key relations.
The ObjectSpace object is part of the WilsonORMapper
libraries and is the main coordinator that is used by the developer to access
data through the framework. It is best to only have one instance of the
ObjectSpace object in an application. Doing so will help manage concurrency in
the application between the data source and the mapper and secondary; the
object loads up the xml configuration file from disk. So, obviously you do not
want to repeatedly read from disk or recreate the object over and over. See
Listing 2 for an example of how to create the object, in our case and by
example from Wilson, the object is static and named Manager.
public class Global
private static ObjectSpace _Manager;
public static ObjectSpace Manager
if (_Manager == null)
private static void CreateORMapper()
string mappingFile = System.Web.HttpContext.Current.Server.MapPath("~/trader.config");
_Manager = new ObjectSpace(mappingFile, ConnectString, Provider.Sql2005);
Let us take a look at a method that retrieves data from our
source by doing a simple SELECT in Listing 3 below.
public Event GetEvent(int Id)
return (Event)Global.Manager.GetObject(typeof(Event), Id);
This method simply returns a filled Event object via the Id
passed into the method. We use the GetObject method on the Manager object,
which is of type ObjectSpace, and tell it the type of object to return. The
mapper then knows what table the object type is mapped to and does the
translation for us. The GetObject method can also then take in the parameter
here, which is simply doing a query via the primary key setup in the
Notice here how everything is strongly typed. The code does
not have to know anything about ADO.NET or even the internals of the Event
object. The mapping loads the right result set fields into the created Event
object properties and fields.
On a side note, it is a matter of
preference if you map your table fields to properties of the object or fields.
The more draconian OOP bylaws would say that if you’re setting data in an
object it should be via properties. But here, the mapper can access everything
inside of the object anyway – so, it is a matter of preference.
Returning one object is simple, but the world works in large
numbers, so let us look at returning a collection of objects. See Listing 4
public IList GetEvents(string sortOrder)
ObjectQuery oq = new ObjectQuery(typeof(Event), "", sortOrder);
ObjectSet allEvents = Global.Manager.GetObjectSet(oq);
The GetObjects method of the Manager object returns a
collection object called an ObjectSet, which is part of the WilsonORMapper
libraries. Being a well behaved collection it implements the IList interface.
So our GetEvents above can be typed to IList to add some flexibility.
Insert, Update and Delete
Doing the other parts of the CRUD cycle (create, update and
delete) are very similar. Listing 5 below shows an insert and Listing 6 shows
an update. These are all working against that same Event type.
public void Insert(Event theEvent)
public void Update(Event theEvent)
The basics of this operation are that the methods first call
a WilsonORMApper method called StartTracking on the Manager object and then
does the persistence. StartTracking lets the mapper know that this object and
its associated row in the database are going to have work done to it. I am not
sure if WilsonORMapper locks the row then or not, but it does add safety to the
operation. Part of StartTracking is telling the mapper if we will be inserting
and updating data. Then we call the PersistChanges method and lastly end our
tracking of that object and database row.
Performing a delete is very similar and can be seen in
public void Delete(Event theEvent)
The only addition with the delete is we call MarkForDeletion
which is another trigger letting the mapper know what we are about to do to
that object and its data.