Relations and custom collections

07 April 2006

A recent discussion in the newsgroup prompted me to summarize the important things to know about creating relationships between classes, using collection type properties, and how to use derived collection types instead of the standard ones.

One-to-many relationships

A one-to-many relationship needs at least the reference on the “many” side. As an example, consider two classes Artist and Album. The Album class must have this:

  public class Album : XPObject {
    ...
    Artist artist;
    [Association("Artist-Albums")]
    public Artist Artist {
      get { return artist; }
      set {
        if (artist != value) {
          Artist oldValue = artist;
          artist = value;
          OnChanged("Artist", oldValue, artist);
        }
      }
    }
    ...
  }

On the other side of the relation, the collection type property is optional. You'll normally add it for one of three reasons:

  1. You actually need the direct access to all the albums of an artist.
  2. You like the the neatness of having everything defined completely.
  3. You want to aggregate the collection by using the AggregatedAttribute, which works only on the "one" side of the relation.

The code on the “one” side looks like this:

  public class Artist : XPObject {
    ...
    [Association("Artist-Albums", typeof(Album)]
    public XPCollection Albums {
      get { return GetCollection("Albums"); }
    }
    ...
  }

There are two interesting variants of the syntax above:

  1. If you use XPO 6.1 with VS 2005, you can use the generic variants of the XPCollection and the GetCollection method. In turn, you can leave out the type parameter of the AssociationAttribute, because the type is then inferred by the generic type attribute. This is how it would look:
      public class Artist : XPObject {
        ...
        [Association("Artist-Albums"]
        public XPCollection<Album> Albums {
          get { return GetCollection<Album>("Albums"); }
        }
        ...
      }
    
  2. To aggregate the collection, you could add the AggregatedAttribute on the collection property.

Many-to-Many relationships

Based on the above, many-to-many relationships are very easy to explain: they need a collection type property on both ends, each of them similar to the one described for the “one” side of the one-to-many relationship.

Custom collection types

Sometimes there might be reasons to derive custom collection types from the standard XPCollection or the XPCollection<T>. This is a lot easier to do in XPO 2006 than in XPO 1 because only one such type (if we neglect the “standard” vs. the generic variant for a moment) is used, i.e. the GetCollection method always return an XPCollection and GetCollection<T> always returns XPCollection<T>. In XPO 1 this used to be different, in that GetCollection would return XPOneToManyCollection or XPManyToManyCollection, depending on the relation type of the property – so the developer was forced to create three different derived types to cover all eventualities.

The standard types XPCollection and XPCollection<T> have a special constructor that is used for the case where they must be initialized as relationship collections. This specific collection constructor is relevant in two ways:

  1. You must make sure that your derived collection type implements it. This is not difficult, because you just need boilerplate code to pass through the parameters to the base class constructor, but you must not leave it out.
  2. In relation properties on the “one” side of a one-to-many relationship, or in the properties on both sides of a many-to-many relationship, you must use this constructor to create the collection of your derived type. This is also boilerplate code, but you must get it right.

So, assuming you had a derived collection class MyCollection<T>, this might look similar to this code (of course there might be other constructors and probably other code that implements your specific functionality):

  public class MyCollection<T> : XPCollection<T> {
    ...
    public MyCollection<T>(Session session, object theOwner, XPMemberInfo refProperty) :
      base(session, theOwner, refProperty) { }
    ...
  }

And to use this collection type in relation property, you’d have to write your code like this:

  public class Artist : XPObject {
    ...
    MyCollection<Album> myCollection;
    [Association("Artist-Albums"]
    public MyCollection<Album> Albums {
      get { 
        if (myCollection == null)
          myCollection = new MyCollection<Album>(Session, this, ClassInfo.GetMember("Albums"));
        return myCollection;
      }
    }
    ...
  }

The code in the property getter is essentially the same code that's contained in the GetCollection<T> method – but because that method doesn’t return our own collection type we have to replace it.

References

Tags
5 comment(s)
XPO
This has been posted before to the XPO beta newsgroup, but now that the beta is over, I thought it might...
10 April, 2006
David Shannon
When using custom collections in relation properties, you recommend the following code:

if (myCollection == null)  
        myCollection = new MyCollection<Album>(Session, this, ClassInfo.GetMember("Albums"));  
        return myCollection;  

XPO uses a little bit more code for the same purpose:

if(result == null) {
         result = new XPCollection(this.Session, this, GetCollectionProperty(propertyName));
         Collections.Add(propertyName, result);
}
return result;

The difference is the call to Collections.Add.  What benefits of the framework code do we lose if we omit the call to Collections.Add?  Thanks for any info you can give me on this...
27 June, 2006
Oliver Sturm (DevExpress)
David - none at all, if I'm not completely mistaken. The base object in the XPO library needs to store any number of collection references, and so it uses a collection to do that, called "Collections". When you write your own class and use this pattern for your custom collection, you can usually just use a field in the class for the same purpose, as you have only that one collection reference to store (or at least you have a known number of collections, in contrast to the XPBaseObject).
27 June, 2006
XPO

This has been posted before to the XPO beta newsgroup, but now that the beta is over, I thought it might

28 March, 2008
Jide Ogundipe

I'm evaluating xpo for development of a 3-tier application. I intend to be able to have separate entity objects which maintain the business rules. This is why I like the attribute driven persistence provided by xpo. I'm however having some problems with the implementation of one-to-many or many-to-many relationships. I want to be able to validate or check an object before it is added to the xpcollection that holds the many objects on the one side. I've checked through the documentation and all I have found is an event that is triggered after the item is added. I can handle this event and then remove the item if it violates the business rules, but this feels very unnatural. Is there a way of reacting to additions to the collection, or can I make the association private and hence the collection and then provide add/remove methods on the one side? Even if I do this, is there a way of returning a readonly version of the xpcollection

9 June, 2008

Please login or register to post comments.