Inheritance, when used wisely, can greatly assist in developing expressive, maintainable domain models that neatly capture potentially complex domain behaviours. LightSpeed supports inheritance through the popular single table inheritance and class table inheritance patterns, which encapsulate different ways of mapping an inheritance hierarchy to a relational model. Regardless of the database mapping, the inheritance relationship is modelled using the familiar .NET inheritance feature.
To derive one entity class from another:
· Designer: Choose the Inheritance arrow from the toolbox and drag an arrow from the derived to the base entity; or select the derived entity, go to the Properties grid, and set its Base Class to the desired entity type.
· Code: Use the normal C# or Visual Basic inheritance syntax.
The key aspect of implementing single or class table inheritance is to define a “discriminator” column. A discriminator column is simply a column used by LightSpeed to determine the type of entity to instantiate when loading a row from the underlying table. Quite often this column is also a foreign key to an associated reference data table. E.g. Employee has an EmployeeTypeId.
Every inherited class in single or class table inheritance must specify a discriminator column. All classes in an inheritance hierarchy must specify the same discriminator column.
Furthermore, each class in the hierarchy (except the root class) must specify a discriminator value. This tells LightSpeed, “If the discriminator column contains this value, materialise the row as this type of entity.” Each class must therefore specify a different discriminator value. If LightSpeed encounters a row which doesn’t match any of the derived class discriminator values, it treats it as an instance of the root class.
To specify the discriminator for a derived class:
· Designer: Select the inheritance arrow from the derived to the base class, and fill out the Discriminator Name, Discriminator Type and Discriminator Value settings. If you set the Discriminator setting on the root class, LightSpeed will default the name and type for you.
· Code: Apply DiscriminatorAttribute to the derived class, specifying the discriminator attribute name and the value that identifies this class.
By default, LightSpeed maps inheritance hierarchies to the database using the popular Single Table Inheritance pattern (STI). As the name suggests, the key idea behind this pattern is that all of the data for a particular inheritance hierarchy is stored in a single table. As with most design patterns there are various trade-offs associated with the STI pattern, the most obvious being the classic time/space tradeoff. The STI pattern trades space for time – by storing the data in one table, querying and persisting the data becomes inherently simple and efficient. However, for hierarchies where the entities have different attributes, the underlying table may become sparsely populated. That said, most modern database systems are reasonably good at optimizing unused table space.
You don’t need to do anything special to specify single table inheritance: if you follow the steps above, single table inheritance is what you will get.
When an inheritance hierarchy contains derived classes with a lot of state that is specific to those derived classes, single table inheritance results in an unnatural database design where the table contains many columns that are applicable only to specific subclasses. For situations like these, LightSpeed also supports Class Table Inheritance. In this mapping mode, there is a separate table for each class in the hierarchy, with each table containing columns only for the fields introduced in that class (plus an Id column). This provides a more natural database design at the expense of producing more complex and therefore less efficient queries (because LightSpeed must join the tables on each query).
Here is how a class table inheritance structure appears in the domain model, and how it maps to the database.
Class table inheritance is set up in much the same way as single table inheritance: you must provide a “discriminator” column on the table corresponding to the root class, and specify a discriminator attribute and value on each derived class. In addition, you must specify class table inheritance:
· Designer: For each inheritance arrow in the hierarchy, set Inheritance Type to ClassTableInheritance.
· Code: Apply InheritanceMappingAttribute to the root class of the hierarchy, with an InheritanceMappingKind of ClassTableInheritance
LightSpeed supports a third form of inheritance, known as concrete table inheritance, which is not discriminated. In concrete table inheritance, each entity type maps to a table, and that table contains columns for each field on the entity type, including inherited ones.
Concrete table inheritance is not polymorphic, so you cannot have associations to a base class or perform queries for a base class. As a general rule, any class that is a base class for concrete table inheritance should be abstract.
Concrete table inheritance is useful when:
· You have a few fields that appear in every table in your database, and want to save re-entering them on each entity class.
· You have some common services such as helper methods which need access to class internals (and therefore cannot be extension methods), but do not contribute fields to the entity.
· You don’t need to load objects polymorphically in a single query.
If you need polymorphism but your derived classes don’t introduce extra state (only behaviour), or at least not too much extra state, use single table inheritance.
Class table inheritance is a good fit when you need polymorphism, but have a lot of columns that make sense only for child entities. However, class table inheritance can introduce a significant performance penalty due to the need to perform joins in the database query, and to update multiple tables when saving, especially if you have a very deep or wide hierarchy. In addition, class table inheritance imposes some limitations (for example around cascade deletes) that are not an issue for single table inheritance. Therefore, you should prefer single table inheritance over class table inheritance unless the derived classes add a lot of new columns.
If you don’t need polymorphism, you should use concrete table inheritance because it provides the best performance and most natural database mapping.