PmMda.Net.Dog.Persistence namespace

Introduction

Contains the classes used to persist data objects and reference codes.

Design decisions

This package is a copy from the java-pmMDA framework.
Differences between java pmMDA and pmMDA.NET:

OR mapper

Introduction

An OR mapper maps the object orientated objects of programming languages to relational databases.

Currently pmMDA.NET supports only the OR mapper NHibernate. It is planned to support other OR mapping frameworks such as ObjectSpaces in feature.

Requirements

An OR mapper used by pmMDA.NET must fullfill the following requirements.

NHibernate

NHibernate is a port of the java project Hibernate. Although there is a port of OJB for .NET ( OJB-NET) we think that NHibernate is the better choice. See the document "pmMDA - OR mapper" (german language) for more information.

Assembly information in NHibernate repository

.NET reflection uses the method Type.GetType to get the Type for a qualified name. NHibernate uses this feature to get the Types of the data objects specified in the *.hbm.xml files (NHibernate config files). The type names need to be fully qualified as described in the MSDN article Specifying Fully Qualified Type Names. This means that pmMDA.NET needs to know the assembly name to generate the repository files (*.hbm.xml) used by NHibernate.

pmMDA.NET generates the NHibernate configuration files using a fully qualified name (namespace.class, assembly) for the data objects to map them to their database table. Therefore pmMDA.NET needs to know the assembly names where the data objects will be compiled to.
But because pmMDA doesn't compile the generated artefacts, it doesn't know the assembly names where the data objects are located after the compilation.

To solve that problem, pmMDA.NET defines a tagged value ".net-nhibernate-assembly" which can be specified in the packages which contain data objects. The value of this tagged value is used to generate the NHibernate configuration files. For a successful compilation and execution of the application generated by pmMDA.NET, the data objects must be placed in that specified assembly.

Properties mapping

Primitive properties

<property
  name="m_name" column="NameColumn" access="field"
  length="255" type="String" not-null="false"
/>

Single properties

<many-to-one
  name="m_administrator" column="AdministratorColumn_FK"
  class="PmMda.Net.Examples.Dnm.Users.User, pmMDA.NET.Examples.Artefacts"
  access="field" not-null="false"
/>

Indexed properties as bag

<bag name="m_domains" access="field">
  <key column="ProviderTable_DomainColumn_FK"/>
  <one-to-many class="PmMda.Net.Examples.Dnm.Users.Domain, pmMDA.NET.Examples.Artefacts"/>
</bag>

Indexed properties as set

<set name="m_units" access="field">
  <key column="DomainTable_UnitColumn_FK"/>
  <one-to-many class="PmMda.Net.Examples.Dnm.Users.Unit, pmMDA.NET.Examples.Artefacts"/>
</set>

Indexed properties as set

<list name="m_domains" access="field">
  <key column="ProviderTable_DomainColumn_FK"/>
  <index column="DomainTable_DomainColumn_INDEX"/>
  <one-to-many class="PmMda.Net.Examples.Dnm.Users.Domain, pmMDA.NET.Examples.Artefacts"/>
</list>
Timestamps

Timestamps are used to implement the optimistic concurrency strategy. A changed data object that is stored must have the same timestamp as the data in the database. If the timestamps differ, an other transaction has modified the data object while modifying. In that case the storage of the data object will fail, throwing an exception.

Built-in timestamp feature

NHibernate has a built-in timestamp feature. Unfortunately pmMDA.NET cannot use this feature.

Problem description

In example we have an object "A" which references 4 objects of type "B" within the indexed property "bs".
Assume the full data object graph was first loaded from the database. Now we want to delete 3 of the 4 "B" objects in the same transaction. Untill we commit the transaction, NHibernate will collect the SQL statements to execute. On commit, the statements are ordered and executed. Deleting the 3 "B" object will result in the following SQL statements:

UPDATE BTable SET A_B_FK=NULL WHERE A_B_FK=A.Id
DELETE * FROM BTable WHERE ID=B1.Id AND TS=B1.Timestamp
DELETE * FROM BTable WHERE ID=B2.Id AND TS=B2.Timestamp
DELETE * FROM BTable WHERE ID=B3.Id AND TS=B3.Timestamp

Unfortunately the update statement will be executed before the delete statements (not configurable or changable).
The update statement will modify the TS column of all "B" objects in the database. When the first delete statement is executed the column with the timestamp B1.Timestamp can not be found, because it has changed during the execution of the update statement.

NHibernate assigns the timestamp to the current server time. This would be very bad if there are more than one PersistenceHandler are running on different server machines. The approach of pmMDA is slightly different. The timestamp value should be calculated and assigned by the underlying database, so more than one PersistenceHandler are possible. So the NHibernate timestamp implementation conflicts the optimistic concurrency approach of pmMDA and can not be used.

Solution

pmMDA doesn't use the NHibernate timestamp feature. It will handle the timestamp rows as NHibernate properties.

Example:

<property name="m_timestamp" column="TS"
  type="PmMda.Net.NHibernate.PmMdaTimestampType, pmMDA.NET.Common"
  not-null="false" access="field"
/>

The timestamp property has a special type (PmMdaTimestampType). This type ensures that the imestamp of an owner of indexed properties is updated if items were removed or added, even if no column of the corresponding table has changed.
This is not the normal behaviour, because in the database there is no relation from the owner to its items. Only a foreign key from the items to their owner defines the items of an indexed property. Adding or removing items will therefore not modifiy any column of the owner table.
The PmMdaTimestampType type ensures that the timestamp value, which is assigned to the update and insert statements will be null. This ensures that the timestamp value of the owner will be updated, even if no other column has been modified.

Because in our solution NHibernate doesn't add the timestamp to the where clause of the SQL statements, the optimistic concurrency must be implemented manually. The Persistence handler (see the PersistenceHandler class in the API) checks the timestamp before the data objects are updated or deleted. If the timestamp is not equal to the current database timestamp the data object has been modified by another trannsaction. In that case an exception is thrown. This is bad for performance problems because the timestamp of modified object must be read from the database before the updates are executed.

Isolation level

The isolation level can be specified in the configuation file (App.config). Note that the specified isolation level must be supported by the used database. Fortunately MySQL supports all ordinary isolation level: "Read Uncommitted", "Read Committed", "Repeatable Read" and "Serializable"

PersistanceHandler

The PersistanceHandler defines the methods Store, Retrieve and Delete. To create and store a data object, use the default constructor and the Store method. To update a data object, retrive it using the Retrieve method and store it using.the Store method.

Retrieve method

The Retrieve method loads a data object specified with its identifier and type. All referenced data objects will be loaded immediately.

Store method

The Store method stores a data object. The data object to store may be a new data object. It may also contain new or changed data objects as references. If a referenced data object was removed from the root object the persistence handler deletes this object from the database.

Store method procedures:

  1. Reads the original data object stored in database (orgObject) and collects all data object referenced by the orgObject.
  2. Collects all data object which are referenced by the root object (changedObject) using the visitor.
  3. Compares the data objects referenced by the orgObject with the data object referenced by the changedObject. The result are three lists.
  4. Saves the new objects, updates the modified data objects and deletes the removed objects.

Delete method

The delete method collects all data objects which are referenced from the specified root object. All these objects will be deleted.

Design diagram

Descriptions for the classes, interfaces and types can be found in the API.