Home Articles Talks Links Contact Me ISA ThoughtWorks

Class Table Inheritance


Represent an inheritance hierarchy of classes with one table for each class

How it Works

The straightforward thing about Class Table Inheritance is that it has one table per class in the domain model. The fields in the domain class map directly to fields in the corresponding tables.

As with the other inheritance mappings the fundamental approach of Inheritance Mappers applies.

One issue is how to link together the corresponding rows of the database tables. One scheme is to use a common primary value, so the row of key 101 in the footballers table and the row of key 101 in the players table correspond to the same domain object. Since the superclass table has a row for each row in the other tables, the primary keys are going to be unique across the tables if you use this scheme. An alternative is to let each table have its own primary and use foreign keys into the superclass table to tie the rows together.

The biggest implementation issue with Class Table Inheritance is how to bring the data back from multiple tables in an efficient manner. Obviously making a call for each table isn't nice since you have multiple calls to the database. You can avoid this by doing a join across the various component tables. However joins for more than three or four tables tend to be slow due to the way databases do their optimizations. You also need to often query the root table first to find what kind of object you need to load and then do a second query to get the full collection of data. To do this well you need to store a type code in the root table, without a type code you need to search each table to get the value.

When to Use it

Class Table Inheritance, Single Table Inheritance and Concrete Table Inheritance are the three alternatives to consider for inheritance mapping.

The strengths of Class Table Inheritance are:

The weaknesses of Class Table Inheritance are:

You don't have to choose just one inheritance mapping pattern for one class hierarchy. You could use Class Table Inheritance for the classes at the top of the hierarchy and a bunch of Concrete Table Inheritance for those lower down.

Further Reading

A number of IBM texts refer to this as Root-Leaf mapping [Brown et al]

Example: Players and their kin (C#)

Here's an implementation for the sketch. Again I'll follow the familiar (if perhaps a little tedious) theme of players and the like using Inheritance Mappers

Figure 1: The generic class diagram of Inheritance Mappers

Each class needs to define the table that holds the data for the class and a type code for the class.

class AbstractPlayerMapper... 
		abstract public String TypeCode {get;}
		protected static String TABLENAME = "Players";
class FootballerMapper... 
		public override String TypeCode {
			get {return "F";}
		}
		protected new static String TABLENAME = "Footballers";

Unlike the other inheritance examples, this one does not have a overridden table name, since we have to have the table name for this class even when the instance is an instance of the subclass.

Loading an object

If you've been reading the other mappings, you'll know the first step is the find method on the concrete mappers.

class FootballerMapper... 
		public Footballer Find(long id) {
			return (Footballer) AbstractFind (id, TABLENAME);
		}

The abstract find method looks for a row matching the key and, if successful, creates a domain object and calls the load method on that object.

class Mapper... 
		public DomainObject AbstractFind(long id, String tablename) {
			DataRow row = FindRow (id, tableFor(tablename));
			if (row == null) return null;
			else {
				DomainObject result = CreateDomainObject();
				result.Id = id;
				Load(result);
				return result;
			}
		}
		protected DataTable tableFor(String name) {
			return Gateway.Data.Tables[name];
		}
		protected DataRow FindRow(long id, DataTable table) {
			String filter = String.Format("id = {0}", id);
			DataRow[] results = table.Select(filter); 
			return (results.Length == 0) ? null : results[0];
		}
		protected DataRow FindRow (long id, String tablename) {
			return FindRow(id, tableFor(tablename));
		}
		protected abstract DomainObject CreateDomainObject();
class FootballerMapper... 
		protected override DomainObject CreateDomainObject(){
			return new Footballer();
		}

There is one load method for each class which loads the data defined by that class.

class FootballerMapper... 
		protected override void Load(DomainObject obj) {
			base.Load(obj);
			DataRow row = FindRow (obj.Id, tableFor(TABLENAME));
			Footballer footballer = (Footballer) obj;
			footballer.club = (String)row["club"];
		}
class AbstractPlayerMapper... 
		protected override void Load(DomainObject obj) {
			DataRow row = FindRow (obj.Id, tableFor(TABLENAME));
			Player player = (Player) obj;
			player.name = (String)row["name"];
		}

As with the other sample code, but more noticeably in this case, I'm relying on the fact that the ADO.NET data set has brought the data from the database and cached it into memory. This allows me to make several accesses to the table based data structure without a high performance cost. If you are going directly to the database, you'll need to reduce that load. For this example you might do this by creating a join across all the tables and manipulating that.

The player mapper determines which kind of player it needs to find, and then delegates the correct concrete mapper.

class PlayerMapper... 
		public Player Find (long key) {
			DataRow row = FindRow(key, tableFor(TABLENAME));
			if (row == null) return null;
			else {
				String typecode = (String) row["type"];
				if (typecode == bmapper.TypeCode)
					return bmapper.Find(key);
				if (typecode == cmapper.TypeCode)
					return cmapper.Find(key);
				if (typecode == fmapper.TypeCode)
					return fmapper.Find(key);
				throw new Exception("unknown type");
			}
		}
		protected static String TABLENAME = "Players";

Updating an object

The update method appears on the mapper superclass

class Mapper... 
		public virtual void Update (DomainObject arg) {
			Save (arg);
		}

It's implemented through a series of save methods, one for each class in the hierarchy.

class FootballerMapper... 
		protected override void Save(DomainObject obj) {
			base.Save(obj);
			DataRow row = FindRow (obj.Id, tableFor(TABLENAME));
			Footballer footballer = (Footballer) obj;
			row["club"] = footballer.club;
		}
class AbstractPlayerMapper... 
		protected override void Save(DomainObject obj) {
			DataRow row = FindRow (obj.Id, tableFor(TABLENAME));
			Player player = (Player) obj;
			row["name"] = player.name;
			row["type"] = TypeCode;
		}

The player mapper's update method overrides the general method to forward to the correct concrete mapper.

class PlayerMapper... 
		public override void Update (DomainObject obj) {
			MapperFor(obj).Update(obj);
		}
		private Mapper MapperFor(DomainObject obj) {
			if (obj is Footballer)
				return fmapper;
			if (obj is Bowler)
				return bmapper;
			if (obj is Cricketer)
				return cmapper;
			throw new Exception("No mapper available");
		}

Inserting an object

The method for inserting an object is declared on the mapper superclass. It has two stages, first creating new database rows to hold the data, secondly using the save methods to update these blank rows with the necessary data.

class Mapper... 
		public virtual void Update (DomainObject arg) {
			Save (arg);
		}

Each class inserts a row into its table

class FootballerMapper... 
		protected override void AddRow (DomainObject obj) {
			base.AddRow(obj);
			InsertRow (obj, tableFor(TABLENAME));
		}
class AbstractPlayerMapper... 
		protected override void AddRow (DomainObject obj) {
			InsertRow (obj, tableFor(TABLENAME));
		}
class Mapper... 
		abstract protected void AddRow (DomainObject obj);
		protected virtual void InsertRow (DomainObject arg, DataTable table) {
			DataRow row = table.NewRow();
			row["id"] = arg.Id;
			table.Rows.Add(row);
		}

The player mapper delegates to the appropriate concrete mapper

class PlayerMapper... 
		public override long Insert (DomainObject obj) {
			return MapperFor(obj).Insert(obj);
		}

Deleting an object

To delete an object, each class deletes a row from the corresponding table in the database.

class FootballerMapper... 
		public override void Delete(DomainObject obj) {
			base.Delete(obj);
			DataRow row = FindRow(obj.Id, TABLENAME);
			row.Delete();
		}
class AbstractPlayerMapper... 
		public override void Delete(DomainObject obj) {
			DataRow row = FindRow(obj.Id, tableFor(TABLENAME));
			row.Delete();
		}
class Mapper... 
		public abstract void Delete(DomainObject obj);

The player mapper, again wimps out of all the hard work and just delegates to the concrete mapper.

class PlayerMapper... 
		override public void Delete(DomainObject obj) {
			MapperFor(obj).Delete(obj);
		}


© Copyright Martin Fowler, all rights reserved