Dashboard > ActiveRecord > ... > Documentation > Type hierarchy
Type hierarchy
Added by Daniel Rothmaler, last edited by Daniel Rothmaler on Aug 20, 2007  (view change)
Labels: 
(None)


In this section we present you two approaches to implement type hierarchies using ActiveRecord.

Type hierarchy using a discriminator field

Sample database structure

Consider the following tables:

CREATE TABLE [dbo].[companies] (
    [id] [int] IDENTITY (1, 1) NOT NULL ,
    [client_of] [int] NULL ,
    [name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [type] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
) ON [PRIMARY]

CREATE TABLE [dbo].[people] (
    [id] [int] IDENTITY (1, 1) NOT NULL ,
    [name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
) ON [PRIMARY]

CREATE TABLE [dbo].[people_companies] (
    [person_id] [int] NOT NULL ,
    [company_id] [int] NOT NULL
) ON [PRIMARY]

Using the Discriminator

We want to represent three entities on the same Companies table (Company, Firm, Client). In order to achieve this, we use a discriminator column - in this case the column type.

[ActiveRecord("companies",
  DiscriminatorColumn="type",
  DiscriminatorType="String",
  DiscriminatorValue="company")]
public class Company : ActiveRecordBase
{
    private int id;
    private String name;
    private IList _people;

    public Company()
    {
    }

    public Company(string name)
    {
        this.name = name;
    }

    [PrimaryKey]
    public int Id
    {
        get { return id; }
        set { id = value; }
    }

    [Property]
    public String Name
    {
        get { return name; }
        set { name = value; }
    }
}

[ActiveRecord(DiscriminatorValue="firm")]
public class Firm : Company
{
    private IList _clients;

    public Firm()
    {
    }

    public Firm(string name) : base(name)
    {
    }

    [HasMany( typeof(Client), ColumnKey="client_of" )]
    public IList Clients
    {
        get { return _clients; }
        set { _clients = value; }
    }
}

[ActiveRecord(DiscriminatorValue="client")]
public class Client : Company
{
    private Firm _firm;

    public Client()
    {
    }

    public Client(string name, Firm firm) : base(name)
    {
        _firm = firm;
    }

    [BelongsTo( "client_of" )]
    public Firm Firm
    {
        get { return _firm; }
        set { _firm = value; }
    }
}

Each class can have its FindAll, DeleteAll, only affecting its subset of records on the same table.

Type hierarchy using joined subclasses

Database structure

CREATE TABLE [dbo].[Entity] (
	[id] [int] IDENTITY (1, 1) NOT NULL ,
	[name] [varchar] (50) NULL ,
	[type] [varchar] (10) NULL
) ON [PRIMARY]

CREATE TABLE [dbo].[EntityCompany] (
	[comp_id] [int] NOT NULL ,
	[company_type] [tinyint] NOT NULL
) ON [PRIMARY]

CREATE TABLE [dbo].[EntityPerson] (
	[person_id] [int] NOT NULL ,
	[email] [varchar] (50) NULL ,
	[phone] [varchar] (12) NULL
) ON [PRIMARY]

The base class

[ActiveRecord("entity"), JoinedBase]
public class Entity : ActiveRecordBase
{
    private int id;
    private string name;
    private string type;

    public Entity()
    {
    }

    [PrimaryKey]
    public int Id
    {
        get { return id; }
        set { id = value; }
    }

    [Property]
    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    [Property]
    public string Type
    {
        get { return type; }
        set { type = value; }
    }

    public new static void DeleteAll()
    {
        ActiveRecordBase.DeleteAll( typeof(Entity) );
    }

    public new static Entity[] FindAll()
    {
        return (Entity[]) ActiveRecordBase.FindAll( typeof(Entity) );
    }

    public new static Entity Find(int id)
    {
        return (Entity) ActiveRecordBase.FindByPrimaryKey( typeof(Entity), id );
    }
}

As you see, there's nothing much different than any other ActiveRecord class. Only that you need to specify the JoinedBase attribute.

The subclasses

[ActiveRecord("entitycompany")]
public class CompanyEntity : Entity
{
    private byte company_type;
    private int comp_id;

    [JoinedKey("comp_id")]
    public int CompId
    {
        get { return comp_id; }
        set { comp_id = value; }
    }

    [Property("company_type")]
    public byte CompanyType
    {
        get { return company_type; }
        set { company_type = value; }
    }

    public new static void DeleteAll()
    {
        ActiveRecordBase.DeleteAll( typeof(CompanyEntity) );
    }

    public new static CompanyEntity[] FindAll()
    {
        return (CompanyEntity[]) ActiveRecordBase.FindAll( typeof(CompanyEntity) );
    }

    public new static CompanyEntity Find(int id)
    {
        return (CompanyEntity) ActiveRecordBase.FindByPrimaryKey( typeof(CompanyEntity), id );
    }
}

[ActiveRecord("entityperson")]
public class PersonEntity : Entity
{
    private int person_id;

    [JoinedKey]
    public int Person_Id
    {
        get { return person_id; }
        set { person_id = value; }
    }

    public new static void DeleteAll()
    {
        ActiveRecordBase.DeleteAll( typeof(PersonEntity) );
    }

    public new static PersonEntity[] FindAll()
    {
        return (PersonEntity[]) ActiveRecordBase.FindAll( typeof(PersonEntity) );
    }

    public new static PersonEntity Find(int id)
    {
        return (PersonEntity) ActiveRecordBase.FindByPrimaryKey( typeof(PersonEntity), id );
    }
}

The JoinedKey attribute is necessary to denote which column is the shared primary key.

This technique does not need a separate ID field member of each class (i e the example introduces CompID and PersonID properties and fields) in order to work.

Consequently, another way to use this hiearchy technique is to (1) declare the ID property of the baseclass virtual and then (2) override it in the subclasses (and not introduce a separate field in the subclasses) which (a) forwards to the baseclass property and (b) has the JoinedKey attribute on it. Example for the person-entity:

[ActiveRecord("entityperson")]
public class PersonEntity : Entity
{
    [JoinedKey]
    public override int Id
    {
        get { return base.Id; }
        set { base.Id = value; }
    }

    ...

Again, don't forget to make the Id property of the baseclass virtual.

You don't need to indicate the tablename in the ActiveRecord attribute (e g "entityperson") for this to work, that is just for the mapping to an existing schema.

Site running on a free Atlassian Confluence Community License granted to Castle Project. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.5.4 Build:#809 Jun 12, 2007) - Bug/feature request - Contact Administrators