Objects and Object Identity

Overview

The identity of objects is a very important matter in OO systems and all the more in object persistence systems. So it’s worth elaborating on this for a short while.

Identity of Objects

Each object oriented software development system needs to determine if two object references indicate the same object. (This is not to be confused with the test for identity as with the Equals() function in .NET.) C++ or .NET and many other implementations use pointer comparison for this task. In these systems the pointer (which is mostly equivalent to the object’s memory address) is an unambiguous object id.

Identity in Databases

A similar situation occurs when we’re working with relational databases. There are relationships between rows in different tables. For these relations to be consistent, each table participating in the relationship needs a column with a unique value (often called Primary Key Column) so that different rows of the table can be distinguished. This value is called the database identity.

Identity of persistent Objects

The identity of persistent objects cannot be based on memory addresses. Persistent objects survive the generating processes and are recreated (possibly on another machine) by new processes with the help of information stored in a database. The objects reside at totally different memory positions after reloading. Furthermore different instances of the same project can exist in different processes or on different machines. NDO even makes it possible to have several persistence managers in a single application domain, each of them managing its own instance of the same persistent object.

The ObjectId in NDO

To cope with that NDO works with an ObjectId class. Ideally the ObjectId encapsulates a value that is equivalent to the database Id. Often database Ids are 32 bit integer values, but strings and Guids can be used as well. The NDO ObjectId class encapsulates these data types with the help of the classes StringKey, Int32Key, and GuidKey. Per default NDO tries to use autonumbered integer ids generated by the database.

While autonumbered ids are very handy, they have some drawbacks compared with client generated ids. So you might decide to use client generated ids.

Client Generated ObjectIds

There are certain situations where Client Generated ObjectIds make sense. Suppose you had a class Topic, such that each Topic could have Subtopics of the same type. Suppose further, you want to create subtrees of that Topic tree and make the subtrees persistent. With autonumbered ids you have to store each level of the tree in its own transaction. You’d need a lot of code to manage that. With client generated ids you can store the whole tree in one transaction.

Note that there are databases, which don’t support autonumbered ids, like Oracle or Firebird. In these cases you have to use client generated ids.

The easiest way to implement a client generated id is to use the NDOOidType attribute:

[NDOPersistent, NDOOidType(typeof(Guid))]

public class Employee

{...}

NDO uses Guid ids for this class and generates the ids for you. It is possible to assign this attribute to all persistent classes in an assembly. Just write the following line into the assemblyinfo.cs:

[assembly: NDOOidType(typeof(Guid))];

Note that in this case you need using declarations for the System and NDO namespaces in the assemblyinfo.cs.

There are cases where the application should generate the id by itself. In that case you can use the IdGenerationEvent of the PersistenceManager:

PersistenceManager pm = new PersistenceManager();

pm.IdGenerationEvent += new IdGenerationHandler(this.OnNewId);

...

private OnNewId(Type t, ObjectId oid)

{

    ((Int32Key)oid.Id).Key = GetNewKeyFromSequence();

}

The parameter t makes it possible to use different key sources for different types.

In some cases, the key values have to do with the business logic and therefore have to be coded in the class.

Then you have the possibility to map fields of your class to the ObjectId. Just declare a private persistent variable of an applicable type. NDO supports four data types:

System.Int64

System.Int32

System.String

System.Guid

Guids are stored as string values if a database does not provide a Guid column type.

If the id should be mapped to a field, the name of the field should be entered in the FieldName attribute of the oid entry. You can force the Enhancer to create the FieldName entry automatically with the help of the NDOObjectID attribute:

[NDOPersistent]
class Test

    [NDOObjectId] 
    string id; // mapped to oid 

    // ordinary persistent field 
    string ordinaryPersistentField; 

    ...
}

Important Note: The NDO developers recommend using the Guid id type for all classes in new projects.

Concurrency

NDO makes sure that only one instance of a certain object lives in one PersistenceManager even if you query for it several times with different queries. But you should be aware that concurrent access to the same object can occur if it is used in different processes or on different machines.

Since NDO is based on ADO.NET, record locking is not supported by NDO. In ADO.NET the data is read from the database into an in-memory cache called DataSet. All changes take place in this cache; the data are not written back to the database before the next update. NDO initiates the update if the Save() function of the PersistenceManager is called.

NDO has a mechanism for avoiding access collisions that is based on TimeStamp-Guids. The next chapter covers this in detail.