Contains the classes used to persist data objects and reference codes.
This package is a copy from the java-pmMDA framework.
Differences between java pmMDA and pmMDA.NET:
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.
An OR mapper used by pmMDA.NET must fullfill the following requirements.
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.
.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.
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 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.
NHibernate has a built-in timestamp feature. Unfortunately pmMDA.NET cannot use this feature.
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.
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.
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"
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.
The Retrieve
method loads a data object specified with its identifier and
type. All referenced data objects will be loaded immediately.
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:
The delete method collects all data objects which are referenced from the specified root object. All these objects will be deleted.
Descriptions for the classes, interfaces and types can be found in the API.