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. |
