In the previous part I described the reasons for Hierarchy & Key terms introduction. Today we’ll continue with this topic and see how these concepts are implemented in DataObjects.Net 4. Let’s start with Hierarchy.
Hierarchy is entirely defined by its root element. This is how we do it:
[HierarchyRoot] public class Animal : Entity { [Field, Key] public int Id { get; private set; } [Field] public string Name { get; set; } }
Note that the class is marked with HierarchyRoot attribute and it contains one field that is marked with Key attribute. Thus we explicitly define structure of Key for this hierarchy (and by that for every class that belongs to this hierarchy).
Also remember, that all root’s descendants belong to the same hierarchy. Class could belong to one hierarchy only at a time. This means that an attempt to define a new hierarchy root inside existing hierarchy is considered as error.
Another option, that is exposed by HierarchyRoot attribute, is inheritance mapping scheme. DataObjects.Net 4 supports the following schemes:
- ClassTable is the default one. It represents an inheritance hierarchy of classes with one table for each class. It is ideal for deep inheritance hierarchies and queries returning base classes. This case implies joins + a single base table for the whole hierarchy.
- SingleTable represents an inheritance hierarchy of classes as a single table that has columns for all the fields of the various classes. This kind of mapping is more preferable for tiny inheritance hierarchies, or for hierarchies where there is a set of abstract classes and a non-abstract single leaf.
- ConcreteTable represents an inheritance hierarchy of classes with one table per concrete class in the hierarchy. This kind is ideal when you always query for _exact_ types of objects stored there. I.e. you query not for Animal, but for Dog (which is a leaf type in hierarchy). When you query for Animal here, there will be UNION in query.
More detailed description of inheritance hierarchy mappings can be found in Martin Fowler’s book “Patterns of Enterprise Application Architecture”.
This is how we can define inheritance mapping for the given hierarchy:
[HierarchyRoot(InheritanceSchema=ConcreteTable)] public class Animal : Entity ...
As you’ve already seen – DataObjects.Net 4 gives more freedom to the way how persistent classes can be mapped to database tables. But more freedom also means more responsibility as it is required that every persistent class from domain model must belong to some explicitly defined hierarchy. Let’s look at the following domain model:
Classes marked with orange color (Dog, Cat) are hierarchy roots, but Animal – is not. As Animal class doesn’t belong to any hierarchy, DataObjects.Net 4 can’t make any decision on how Animal type is mapped to database schema; how instances of Animal type should be persisted and fetched; and even what is the structure of key for Animal type. So should this Domain model be considered as erroneous or not? We think, no. In this case Animal class could be safely removed from Domain model because every descendant of Animal class is correctly mapped. All persistent fields that are defined in Animal class will be copied into Dog & Cat classes.
In the next model such kind of removal is impossible:
Again, classes marked with orange color (Dog, Cat) are hierarchy roots, whereas Animal & Horse are not. DataObjects.Net 4 can’t make any mappings for Horse, and moreover it can’t be safely removed because we can’t make any conclusions about Horse’s hierarchy: it doesn’t have any descendants at all. It seems that in this case domain model author simply forgot to mark Horse class with HierarchyRoot attribute. So he will get a DomainBuilderException then.
One more erroneous scenario:
One persistent class (Cat) has a reference to another persistent class (Horse), but second one doesn’t belong to any hierarchy (thus its key structure is not known), so DataObjects.Net 4 can’t choose the structure of reference field in Cat table and DomainBuilderException will be thrown.
More freedom => more responsibility.
You say:
ReplyDelete"ClassTable is the default one. It represents an inheritance hierarchy of classes with one table for each class. It is ideal for deep inheritance hierarchies and queries returning base classes."
Not so sure about this as one of my biggest problems with V3 was the 'a single base table for the whole hierarchy'. If you based a deep hierarchy on this you could end up with lots of database locking problems, as per version 3 of DO.
Hello Tony,
ReplyDeleteYes, you are absolutely right. Single hierarchy with ClassTable mapping scheme for huge persistent model could really lead to numerous database locking issues. That is why we decided to remove former single hierarchy restriction from DO v4 and give developers the feature to define arbitrary number of hierarchies with different mapping schemes. So with DO v4 one can choose the most effective mapping scheme for every hierarchy of domain model.