Hidden members in ObjectSpaces

If you have worked with ObjectSpaces you might wonder what hidden members are all about. The documentation is pretty slim here:

"If you do not want to display the unique identifier value for a particular class, you can use the Hidden attribute to specify whether the Member is visible by the class or not."

Here is a sample of how you might use the hidden attribute on a member declaration in your .osd file:

<osd:Class Name="Alex.Thissen.Person">

  <osd:Member Name="Id" Key="true" KeyType="AutoIncrement" Hidden="true"/>

  <osd:Member Name="Name"/>

  <osd:Member Name="Birthday"/>

</osd:Class>

If you try to hide a member that exists in the corresponding class, you will get a ObjectSchemaException. So how would you use it, then?

Well, here's the lowdown on hidden members. The hidden members exist to create fields that will be mapped, but do not exist in your class. You could call them virtual fields, because these fields do exist for ObjectSpaces, are persisted in the data source, but have no corresponding member in the persistent class. To put it another way, the hidden members are retrieved from the data source, are tracked inside the ObjectContext, but just don't make it inside the objects as a actual member. Hidden members are inaccessible from your objects.

So far, I figured that there are two possible uses for hidden members:

  1. Create a key member for a class that does not have one itself.
  2. Use extra members that are used for concurrency checks when updating or deleting objects from the data source.

Let me elaborate a little.

On point 1:

When your class that needs to be persisted does not have a key member itself, you have the choice to add such a member to your class, or not. For the second choice you can specify a key member and mark it as hidden. This can be very handy in cases where you have no access over the source code of the class. Again, the hidden member is used to mark the key field as non-existent in the persistent class. However, since it does exist in the ObjectContext and is mapped, the member/field makes it way into the data store when the objects are persisted. This way you can still add a key to your objects from a database perspective, without making changes to the class.

The key member will need to have a key type. Any key type is possible, except for the User type. This leaves you with AutoIncrement, Guid and Custom. The reason that you can't use User is simple: you have no way to programmatically set the value of the member. It only exists in the ObjectContext, not in the object. The engine of ObjectSpaces will take care of assigning it for you, based upon the type of key. It will generate an autoincrement integer, a new Guid or use the KeyGenerator class if you specified a custom key type.

You probably got it by now. Let me just add the context of it. Let's assume you map the Person class:

public class Person

{

  public string name;

  private DateTime birthday;

  public Person(string name, DateTime birthday)

  {

    this.name = name;

    this.birthday = birthday;

  }

}

that has an osd declaration like above. The .msd file will map all three members (one of which is not represented in the class) to the data store.

<map:Map SourceVariable="Person_Rows" TargetSelect="Person">

  <map:FieldMap SourceField="Id" TargetField="Id"/>

  <map:FieldMap SourceField="Name" TargetField="Name"/>

  <map:FieldMap SourceField="Birthday" TargetField="Birthday"/>

</map:Map>

The database table Persons will have three columns, for Name, Birthday and Id.

<rsd:Table Name="Person">

  <rsd:Columns>

    <rsd:Column Name="PersonID" SqlType="int" AutoIncrement="true" IncrementStep="1"/>

    <rsd:Column Name="Name" SqlType="nvarchar"/>

    <rsd:Column Name="Birthday" SqlType="datetime"/>

  </rsd:Columns>

</rsd:Table>

You can check for yourself that the ObjectContext does contain the hidden member, where (obviously) the Person class does not have such a member:

SqlConnection con = new SqlConnection("Data Source=localhost; Integrated Security=true;INITIAL CATALOG=Demo");

ObjectSpace os = new ObjectSpace("Demo.msd.xml", con);

Person person = new Person("Alex Thissen", DateTime.Now);

os.StartTracking(person, InitialState.Inserted);

 

try

{

  os.PersistChanges(person);

}

catch (Exception ex)

{

  Console.WriteLine(ex.ToString());

}

 

CommonObjectContext ctx = (CommonObjectContext)ObjectContext.GetInternalContext(os);

ValueRecord record = ctx.GetCurrentValueRecord(person);

Console.WriteLine(record["Id"]);

This will print the newly assigned key member value from the ObjectContext.

As a side note: the Object Persistence Toolkit (OPT) also makes use of this strategy to create mapping files and database tables for every class that you ask it to persist. It will add a hidden key member to the class, as it is unable to determine for a particular class (say the Person class from above) if there is a member that could serve as the key member. It will add a new one to avoid wrong assumptions based upon the names of member. The OPT restricts the key type to an autoincrement member. I'm not sure why that would be. I've successfully tested with a Guid hidden key member.

On point 2:

Imagine that you created a class that has members for a subset of the columns of a certain table. When you try to update or delete your objects to the data source, you might want to check for concurrency errors. Normally you can only do this based upon the members that are actually retrieved, because they exist in the persistent classes and are mapped. With hidden members you can retrieve not just the columns for the members of your objects, but also additional columns, that are tracked in the ObjectContext. These are by default used for concurrency checks. This way, you can tell when the original record has changed, based upon the checks on database columns/fields that you would be unable to alter yourself.

BTW, it would be a weird combination to mark a non-key member as both Hidden="true" in the osd file and UseForConcurrency="False" in the msd file.

There it is: hidden members. If you have any comments or can think of other uses for hidden members, feel free to leave your thoughts.

Comments

# Frans Bouma said:

Let me first say that you should be rewarded for the amount of time you spend on explaining this to the average programmer :) In November (or was it december?) I saw your presentation of asp.net in whidbey on the DotNed meeting in Amsterdam and it was very good, you are very good in explaining things :)For the hidden member concept: I find it a weak design. If you want to be non-intrusive (and objectspaces want to be that), you shouldn't require a private member to hold a field state if you solely need that field to keep the relational model happy. It's also not necessary, as Objectspaces assures unique objects, so it can find back an id of an entity in its own hashtables.To have it hidden is to prevent a developer setting it to a value. This is however a result of the non-intrusive approach: an intrusive O/R mapper generates classes which implement ITypedList for collections and the entities have internal mechanisms which assure that an exception is thrown when an identity field or other read-only field (PK field after the entity is already been saved once?) is set to a value. A non-intrusive O/R mapper can't do that: it works with normal classes which have get/set properties for each field.But besides that, the developer should be aware of the fact what an entity is, that it has a unique identifying set of attributes and that these attributes thus are exposed by an instance of a class which contains an entity's data.

zondag 21 maart 2004 21:44
# Alex Thissen said:

Hi Frans,Thanks for all the flattering comments. You'll have to stop now, or you will make me blush :) Next time we're at a dotNed meeting (or somewhere else for that matter) we should meet in person. I'm kinda curious. Did we talk to each other at the Whidbey meeting? Anyways, I agree with you on most of your comments. Hidden members should be used only in special cases. One of those special cases would be related to the lengthy ObjectSpaces newsgroup discussion on natural, logical and surrogate keys (which when I think of it makes my head spin again). What if you decide to add a surrogate key to your database table, because your persistent class has a natural key that you do not want to use as a PK? (Correct me if I am wrong on the types of keys.) You would be able to add it in a non-intrusive way. The user of the objects from the persistent class would not need to be able to access this key anyway.And Frans, what do you think of hidden members in a non-key scenario, such as the concurrency checking? Bad or not?

maandag 22 maart 2004 23:14