Object-relational Map

Summary

Purpose

Decouples active domain objects from the underlying data model and data access details. An Object-Relational Map object is responsible for mapping relational data to object-oriented concepts, allowing it to be changed independently of the application and its domain objects.

Scenario

The following figure illustrates how an Object-Relational Map decouples active domain objects from the underlying data and data model:

A sophisticated Object-Relational Map system would map database details using metadata. This versatile approach allows metadata to be changed without recompiling or updating the application. It is common to store mapping metadata in database tables and to define administration tools to allow users to view and update mapping details without having to understand the underlying metadata structure.

The most fundamental issue that an Object-Relational Map deals with is how to map relation database concepts to object-oriented concepts. Domain object mapping is responsible for translating a row from a given table to an object that exposes that row's columns as properties. This situation is shown below:

The following table shows a common strategy using C# and ADO.NET

Relational database concept Object-oriented concept
Table class. An instance of this class is a domain object.
Row DataRow object encapsulated within a domain object
Column set/get properties 

In practice, things are not that simple. For example, an Object-Relational Map often hides relational data that an application might not need such as timestamp columns and other columns used purely to define table relationships. An Object-Relational Map may convert data in both directions (for example, BLOBs in Oracle) and calculate attributes on behalf of the application.

For example, the steps for generating an invoice within an order-processing application would be:

Note that the application operates exclusively using domain objects and makes no explicit reference to the data model or to the underlying data accessors.

Implementing Object-Relational Map classes is usually a non-trivial undertaking. Writing an efficient, versatile and metadata-driven Object-Relational Map can be a difficult task. However, there are many commercial products that can be employed directly within your application.

Structure & UML

The above figure represents the static structure of one variety of an Object-Relational Map class. The IPersistenceManager interface defines database operations in terms of  generic domain object - it defines operations for reading/writing/deleting domain objects. This interface does not expose any database details as these are taken care of by the data accessor. A ConcretePersistenceManager implements IPersistenceManager for a specific domain object. A ConcretePersistenceManager reads/writes/deletes domain objects using the help of both MapMetadata class to describe domain object mapping to relational database table and IDataAccessor to access the database. A ConcretePersistenceManager therefore encapsulates data access and data model details as well as domain object mapping.

The sequence of operations begins when an application invokes the read operation on a ConcretePersistenceManager:

The ConcretePersistenceManager finds the relevant metadata that describes the mapping details, obtains relational data from the database and then creates a new domain object and initializes using the relational data and metadata.

Example

This example illustrates the order processing application described in the Scenario section. The active domain object in this example is OrderItem which represents a single item in any given order. The persistence manager is responsible for mapping the contents of OrderItem objects to the [OrderItems] table. At runtime, the persistence manager instantiates an OrderItem object for each row in the [OrderItems] table.  Each property in OrderItem corresponds to a column from [OrderItems] table. In the OrderItem class definition below, note that it is defined using pure object-oriented domain concepts and that there is no reference to the underlying data table: 

public class OrderItem
{
    /* Data members */
    private string m_strProductName;
    private long m_lOrderID;
    private long m_lProductID;
    private long m_lQuantity;
    private float m_fPrice;

    /* Constructor */
    public OrderItem( string name, long oid, long pid, long qty, float prc)
    {
        // Initialize member variables
    }

    /* Properties. Used mostly by persistence manager to set with database values */
    public string ProductName
    {
        get { return m_strProductName; }
        set { m_strProductName = (string)value; }
    }

    public long OrderID
    {
        get { return m_lOrderID ; }
        set { m_lOrderID = (long)value; }
    }

    public long ProductID { get { ... } set { ... } }
    public long Quantity { get { ... } set { ... } }
    public float Price { get { ... } set { ... } }

    /* Other public functions exposed by this domain object */
   
...
}

The Order class serves a similar purpose to OrderItem in that it represents the domain concept of a customer's order using object-oriented semantics. Again, the persistence manage is responsible for mapping the contents of Order objects to the [Orders] table:

public class Order
{
    /* Data members */
    private DateTime    m_dtOrderTime
    private long        m_lOrderID;
    private float       m_fTotal;
    private ArrayList   m_alOrderItems;         // Every Order object references a collection of OrderItems

    /* Constructor */
    public Order( DateTime odt, long oid, float totl, ArrayList items)
    {
        // Initialize member variables
    }

    /* Properties. Used mostly by persistence manager to set with database values */
    public DateTime  OrderTime
    {
        get { return m_dtOrderTime; }
        set { m_dtOrderTime = (DateTime)value; }
    }

    public ArrayList OrderItems
    {
        get { return m_alOrderItems; }
        set { m_alOrderItems = (ArrayList)value; }
    }

    public long  OrderID   { get { ... } set { ... } }
    public float Total     { get { ... } set { ... } }

    /* Other public functions exposed by this domain object */
   
...
}

Note how the Order object references a collection of OrderItems. This models the ONE-TO-MANY aggregation relationship between [Orders] and [OrderItems] tables.

Object-Relational Map objects do not define any mapping details. Instead the developer defines these mapping details using metadata, most commonly in XML. This XML can then either be stored in configuration files, or much-preferably in databases. Complex applications may require some custom-made GUI tools to graphically creates these mappings while less-complex applications may require you to code the XML by hand. A possible XML file for mapping [OrderItems] table to the [Order] domain object is shown below:

<Root>
    <!-- Data store definition --> 
    <DataStore>
        <Database server="..." database="..." uid="..." pwd="..." ></Database>
    </DataStore>

    <!-- Mapping definitions - object property to table field -->
    <MappingObjects>

        <!-- mappings for the OrderItem class -->
        <MappingObject class ="OderItem" table="OrderItems">
            <Property name="ProductName" column="product_name" type="string"></Property>
            <Property name="OrderID" column="order_is" type="long"></Property>
            <Property name="ProductID" column="product_id" type="long" primarykey="true"></Property>
            <Property name="Quantity" column="quantity" type="long"></Property>
            <Property name="Price" column="price" type="float"></Property>
        </MappingObject>

        <!-- mappings for the Order class -->
        <MappingObject class="Order" table="Orders">
            <Property name="OrderTime" column="order_timestamp" type="DateTime"></Property>
            ...
        </MappingObject>

    </MappingObjects>
</Root>

In general, it is easy to write a tool (perhaps a Visual Studio.NET add-in) that can read this XML mapping file and automatically generate code that was previously shown for OrderItem and Order. This tool (or add-in) can actually generate appropriate code and then add it as a new project file. Users will only need to create XML mappings and the tool takes care of generating code.

The next code block shows how all these pieces fit together. The application initializes a persistence manager to read the XML file, connect to the database using the <Database> element to get data, and finally initialize the appropriate mapping objects as indicated by the <MappingObject> elements.

// Create and initialize a persistence manager
PersistenceManager pm = PersistenceManagerFactory.GetPersistenceManager();
pm.Load( strMetadataFilePath );

// Create a new order object with a list of Order items
ArrayList alOrderItems = new ArrayList();
alOrderItems.Add( new OrderItem( ... ) );
alOrderItems.Add( new OrderItem( ... ) );
alOrderItems.Add( new OrderItem( ... ) );
Order order = new Order( ..., alOrderItems)

// Persist the order object to the database
pm.PersisteObject( order );

Applicability

Use this pattern when:

Strategies / Variants

Consider these strategies when designing Object-Relational Map classes

Unmapped attributes

In general, domain objects that participate in Object-Relational Map (like OrderItem and Order in the example above) often correspond to a row in a database table or join. However, not all of an object domain's properties are stored in a database. In some cases, they are computed or aggregated from different fields. These attributes are called unmapped attributes because they do not correspond to a single piece of relational data. Unmapped attributes do not typically present and issues as they can be derived from properties (attributed) already obtained by the domain object.

Object Identity

For a persistence manage to properly map domain objects to their corresponding rows in a table, you must define the notion of identity on both sided of the relationship. If a table defines a primary key, then it already defines a unique identity for its rows. Similarly, if a domain object defines one or more properties that map to the table's primary key, then the domain object has an identity. Two possible reasons for defining object identity are:

Aggregation Relationships

In databases, aggregation involves matching a foreign key in one table to a primary key in another table. Corresponding domain objects model similar relationships, but they translate to object aggregation in which one domain object directly references others (usually through a collection). It is common to describe relationships between tables as one of the following types:

Regardless of the relationship, it is the responsibility of the persistence manager to resolve and manage object B instances when it deals with object A instances. In the previous example, there is a one-to-many relationship between Order and OrderItems classes. The Order class exposes this relationship through its OrderItems collection property.

The persistence manage can implement the corresponding data access operations using one or more of the following strategies:

Inheritance Relationships

It is common for domain objects to relate to each other by inheritance, but relational data does not support a similar concept. For example a Manager class may extend (or specialize) an Employee class by adding attributes that only apply to managers. It is common for a persistence manager to map these domain objects using one of these strategies:

Single table inheritance makes query, batch update and batch deletes simpler and more efficient to implement since they only involve a single table. The same operations using concrete or class table inheritance require joins or multiple database operations. However, single table inheritance does not make the most efficient use of database storage as it requires the table to define the union of all attributes (columns) within an inheritance hierarchy.

Benefits

Liabilities

Related Patterns

The following patterns are related to the object-relation map patterns: