Persistate

References and MetaReferences

Hide Navigation Pane

References and MetaReferences

Previous topic Next topic No directory for this topic  

References and MetaReferences

Previous topic Next topic Topic directory requires JavaScript JavaScript is required for the print function Mail us feedback on this topic!  

At the core of the scheme used by Persistate to address objects in persistent storage is the IReference interface.  This is implemented by the Reference and MetaReference structs, and perhaps counter-intuitively, by the Persistent class, which is the base class of all persistent objects.  The reason for the latter is to allow IReference to be used for the backing fields for properties containing scalar links to objects.  These fields contain a Reference when the object concerned has not yet been fetched from persistent storage, and are set with the referenced Persistent once it has.

All IReference values correspond with a single object in persistent storage.  This object can be held in any database in a single Domain.  The addressing scheme provided by IReference therefore covers the entirety of the persistent tree within one Domain.

Future Enhancement

There is another class which implements IReference, and that is ForeignReference.  This will provide a reference to an object in a foreign domain - one outside the local domain, or more precisely to a local copy of a foreign object.  Although much of the infrastructure is in place for this, the functionality is not yet complete.  When it is, this will extend the range of the IReference addressing scheme to all persistent objects in all domains.

The main members of the IReference interface are as follows.  This is intended to list the capabilities of the interface - see the API documentation for full details.

DeReference(Persistent parentObj).  This returns the Persistent object referenced by the IReference.  The parent object may be null.
ToReference().  This returns a Reference referring to the same object as this IReference.
Database { get; }.  This gets the Database object representing the database in which the referred object is stored.
Class { get; }.  This gets the ObjectClass representing the class of the referred object.
OwningDomain { get; }.  This gets the Domain object representing the domain which contains the referred object.
IsPersisted { get; }.  This returns true if the referred object has been stored in persistent storage.
IsProvisional { get; }.  This returns true if the referred object has not yet been stored in persistent storage.
ObjectNumber { get; }.  This gets a long holding a number for the referred object which is unique for its class within the database in which it is stored.

The Reference and MetaReference structs use fundamentally different schemes for storing the whereabouts of the referred object and performing the dereference, as described in the following sections

The Reference struct

The Reference struct has three fields, namely a database number, a class number and an object number.  The database number indexes the Domain.Databases collection, and the class number indexes the Domain.ObjectClasses collection.  Together, these two fields identify the database in which the referred object is stored, and the table or tables in that database where its fields are kept.  The object number provides a unique identifier within the table.

It can be seen then that a Reference provides what amounts to a compact physical address of an object, which is entirely independent of its location within the persistent tree.  The three fields are held within a single long value.  You might imagine that they would be bit fields, but in fact they are based on powers of 10.  The simple reason for this is that it makes the individual fields easily visible when viewing the raw tables in the database.  The extra overhead involved is pretty trivial compared to the database access required to perform a dereference.

The choice of multipliers for the three fields in a Reference sets limits on the number of databases and classes in the domain, and objects within each class in a database.  The numbers are 10,000,000,000 objects in a class in one database, 10,000 classes in a domain and 92,233 databases in a domain.

Future Enhancement

The way that the fields are partitioned within Reference, and indeed the choice of long to store one, is entirely encapsulated within Reference, and to a smaller extent within the "drivers" for the different DBMS systems below the database abstraction layer.  These could be changed relatively easily and with minimal impact in a future version of Persistate.

The MetaReference struct

The MetaReference struct works in a fundamentally different manner to the Reference struct.  Whereas the latter is essentially a physical address of an object, MetaReference holds the location of an object in the containment hierarchy of the persistent tree.  A MetaReference holds an array of strings.  The first string is the domain name, and each subsequent string is used to index an object at the next level down in the containment hierarchy,  The object indexed by the last string is the referenced object.

In order to get a MetaReference to an object, that object must implement the IMetaReferable interface.  You need to implement two methods.  The IMetaReferable.BuildNames method provides the indexes at each level in the hierarchy to build the MetaReference, and the IMetaReferable.DeReferenceSection method uses the indexes at each level to index the way down the hierarchy to reach the referred object.

Note that the indexes built by BuildNames at one level of the hierarchy are used by DeReferenceSection at the level above it, meaning that MetaReferences work through a collaboration of all the classes in your containment hierarchy.  The best way to illustrate this is with an example.  Many of the classes in the Persistate package implement IMetaReferable, so here is the DeReferenceSection method from the Package class and the BuildNames methods from PersistentClass and ClassCategory, whose indexes it uses.

From Package.cs

/// <summary> Dereferences a section in a <see cref="MetaReference"/> by
/// returning the object indexed by the given collection indicator and name
/// </summary>
/// <param name="nameSection">The collection indicator and name to use to find 
/// the next object to use in the dereference.</param>
/// <returns>The object indexed by the given nameSection.</returns>
/// <exception cref="PersistateDeReferenceException">Thrown if the name section
/// does not index an existing object.</exception>
public IMetaReferable DeReferenceSection(string nameSection)
{
   // get the collection indicator letter at start of section, and branch
   IMetaReferable result = null;
   char indicator = nameSection[0];
   string index = nameSection.Substring(1);
   switch (indicator) {
      case 'P':
         result = PersistentClasses[index];
         break;
      case 'C':
         result = ClassCategories[index];
         break;
      case 'W':
         result = FindWorkspace(index);
         break;
   }
   // if none worked
   if (result == null)
      throw new PersistateDeReferenceException(null, "index from Package", nameSection);
   return result;
}

From PersistentClass.cs

/// <summary> Builds a list of names for use in creating a <see
/// cref="MetaReference"/> to this object or a contained object. </summary>
/// <param name="names">The list in which to place the names.</param>
public void BuildNames(List<string> names)
{
   ((IMetaReferable)Parent).BuildNames(names);
   names.Add("P" + name);   // mark as a PersistentClass
}

From ClassCategory.cs

/// <summary> Builds a list of names for use in creating a <see
/// cref="MetaReference"/> to this object or a contained object. </summary>
/// <param name="names">The list in which to place the names.</param>
public void BuildNames(List<string> names)
{
   ((IMetaReferable)Parent).BuildNames(names);
   names.Add("C" + name);   // mark as a ClassCategory
}

A MetaReference is used in different circumstances than Reference.  Most of the time, References are used, and in fact because of the way Persistate works, you rarely even need to use those. MetaReferences become useful when dealing with serialised object trees (see Serialising and Deserialising ).  When you deserialise a tree, it may be into a different domain from the the one in which it was serialised.  This means that associations in the serialised tree which are to references external to that tree may be broken if the equivalent object in the destination domain has a different Reference.

In these circumstances, a MetaReference may be more successful at determining the required object to associate to.  Because of this, such associations are always serialised as MetaReferences, if the associated object is IMetaReferable.