Inheritance

Genome supports inheritance as a type of abstraction. This topic is covered in Step3, found in the {Genome installation folder}\Samples\GettingStarted\Step3 folder.

Introducing inheritance in a sample domain model

In the current domain model, the customer class does not have any specialised derivations:

Business/Customer before refactoring

Let us refactor customer to specialise into a Company and an Individual:

Business/Customer specialised into Company and Individual after refactoring

Common properties such as Id, Name and Orders are extracted in the base class. Note that Customer also needs to introduce a new persistent field, Type, to discriminate derived types in the database.

For the sake of simplicity, let us specialise Company and Individual only to the extent that their names are represented differently: CompanyName and FirstName, LastName.

Introducing a type discriminator

As mentioned already, the Customer base class introduces a type discriminator which allows Genome to distinguish object instances retrieved from the database. Unlike the CLR, where a type identity is maintained for each object instance, the database of course does not contain any runtime type information by default to discriminate polymorphic table rows. A common approach in database design is to introduce a persistent field in the base table that contains a different value depending on the type represented in the row.

Genome calls this field the type discriminator or type identity provider of a class hierarchy stored in the database. Whenever you want to store polymorphic instances of a class hierarchy in one or more tables with Genome, you need to introduce a type discriminator property on the first class in the class hierarchy associated with a table in the database. In the example above, the Customer specifies the Type property for this matter.

The type discriminator can be of any scalar field type. Typical examples are numbers, strings or GUIDs.

When mapping each type of the class hierarchy, one or more concrete type discriminator values can be specified to identify the type in the database. When using numbers as the field type, you can use an enumeration in the object model to encapsulate the individual type identity values.

In our example, we will introduce an enum that specifies the different Customer types stored in the database:

namespace Business

{

    public enum CustomerType

    {

        Individual = 1,

        Company = 2

    }

}

Business/CustomerType.cs

As we do not intend to store pure Customer instances in the database, the enum only specifies discriminator values for Individual and Company.

Implementing a base class

Next, we refactor the Customer base class accordingly:

using System;

using TechTalk.Genome;

 

namespace Business

{

    public abstract class Customer : Persistent

    {

        public abstract Guid Id { get; }

        protected abstract CustomerType Type { get; set; }

        public abstract string Name { get; }

       

        public abstract Set<Order> Orders { get; }

        protected Customer(CustomerType type)

        {

            Type = type;

        }

    }

}

Refactoring Business/Customer.cs

We already discussed the Customer.Type property that identifies individual instances of Individual or Company in the database, which we will add the Customer base class now.

The constructor is changed to initialise the correct type identity value for a Customer instance. The derived classes Company and Individual will call the base constructor of Customer with their according type identity as parameter.

The Name of the customer will not be directly stored in a database field anymore. Instead, the name will be derived from CompanyName or FirstName and LastName, depending on the dynamic type of customer. The property hence becomes read only and the initialisation code for name is removed from the Customer’s constructor.

Mapping a base class

Next, we refactor the mapping of Customer to reflect our new class model structure:

<Type name="Customer">

  <RootInheritance/>

  <PrimaryKey>

    <Key>Id</Key>

  </PrimaryKey>

  <TypeDiscriminator memberName="Type"/>

 

  <Member name="Id">

    <PersistentField NativeIdGenerator="true"/>

  </Member>

  <Member name="Type">

    <PersistentField/>

  </Member>

  <Member name="Orders">

    <Linq>

      <![CDATA[

        DataDomain.Extent<Order>().

        Where(o => o.Customer == this)

      ]]>

    </Linq>

  </Member>

  <Member name="Name">

    <PersistentField />

  </Member>

</Type>

Refactoring Business/Customer.xml

The customer table now holds an additional field called Type for distinguishing the type of the row that is mapped with the <PersistentField/> element.

Note that the <Sealed/> type identity provider has now been replaced by the <TypeDiscriminator/> element to specify the property Type as discriminator for rows in the table Customer.

The <CodeGeneratedProxy/> element has also been removed since we do not intend to have direct instances of Customer. Hence, we also do not need to specify a concrete discriminator value for the Customer base class.

Implementing derived classes

Implementing Individual and Company is straightforward. Both derive from Customer, implement the specialised properties and call the base constructor with the corresponding type identity value:

using TechTalk.Genome;

 

namespace Business

{

    public abstract class Individual : Customer

    {

        public abstract string FirstName { get; set; }

        public abstract string LastName { get; set; }

       

        protected Individual(string firstname, string lastname) :
          base(CustomerType.Individual)

        {

            FirstName = firstName;

            LastName = lastName;

        }

       

        public static Individual Create(
          DataDomain dd, string firstName, string lastName)

        {

            return dd.New<Individual>(firstName, lastName);

        }

    }

}

Business/Individual.cs

using TechTalk.Genome;

 

namespace Business

{

    public abstract class Company : Customer

    {

        public abstract string CompanyName { get; set; }

        protected Company(string name) :
          base(CustomerType.Company)

        {

            CompanyName = name;

        }

       

        public static Company Create(
          DataDomain dd, string name)

        {

            return dd.New<Company>(name);

        }

    }

}

Business/Company.cs

As a convenience, we created a specialised factory method for Individual and Company called New() to wrap the call to the generic Genome factory method of the DataDomain to create new instances. While this is not mandatory, we recommend it as a good practice in order to enable intellisense and compile time checking when creating new instances of Genome persistent objects. To prevent further direct calls to DataDomain.New<T>() to create new instances of Individual and Company, the visibility of both constructors has been set to protected.

Another interesting thing to note is that the two static factory methods need the DataDomain as an additional parameter, as they cannot rely on an existing instance that returns a specific DataDomain (using the this.DataDomain property). Of course the DataDomain provider could be also moved to the Business project, allowing static methods in the Business layer to access the DataDomain directly. However, it is a recommended practice to have the client code to set up and manage the DataDomain and pass it to the business layer as a parameter for static methods instead of having the business layer access the DataDomain directly.

Mapping derived classes

After implementing the new derived classes, they can be simply mapped with Genome forward engineering. Genome offers different strategies for mapping derived classes to a database structure: SharedInheritance maps the derived class into the same table as the base class while JoinedInheritance maps the additional properties of the derived class into an additional table joined to the table of the base class.

These strategies can be applied in any combination to a class hierarchy. Also, the applied strategy can be simply changed using the mapping file, without affecting other code in the mapped business layer.

Mapping Company

Let us map Company into the same table as Customer. Right click on Company and select “Map as shared inherited type”:

Mapping Business/Company.cs with the SharedInheritance pattern

This will yield the following mapping file, generated by the wizard:

<Type name="Company">

  <SharedInheritance/>

  <!-- TODO: please specify discriminator value(s) here -->

  <Discriminator value=""/>

  <CodeGeneratedProxy/>

 

  <Member name="CompanyName">

    <PersistentField/>

  </Member>

</Type>

Generated mapping: Business.Mapping/Company.xml

The wizard has inserted the <SharedInheritance/>element in the mapping of Company. All you need to do is specify the value of the type discriminator field of Company instances, in this case CustomerType.Company.

We also want to specialise the mapping for Name introduced in the Customer base class to return CompanyName. Of course we could override the member in the C# implementation of the class. However, when leaving Name abstract in the C# implementation and mapping it in LINQ, the Name can also be used for filtering and other operations:

<Type name="Company">

  <SharedInheritance/>

  <Discriminator value="Company"/>

  <CodeGeneratedProxy/>

 

  <Member name="CompanyName">

     <PersistentField/>

  </Member>

 

   <Member name="Name" Linq="CompanyName" />

</Type>

Business.Mapping/Company.xml with filled in type discriminator and LINQ mapping for Name

 

Mapping Individual

Mapping with JoinedInheritance is similar with the forward engineering wizard. Just select “Map as joined inherited type” in the context menu for the class Individual:

Mapping Business/Individual.cs with the JoinedInheritance pattern

This will yield the following mapping file generated by the wizard:

<Type name="Individual">

  <JoinedInheritance/>

  <!-- TODO: please specify discriminator value(s) here -->

  <Discriminator value=" "/>

  <CodeGeneratedProxy/>

 

  <Member name="FirstName">

    <PersistentField/>

  </Member>

  <Member name="LastName">

    <PersistentField/>

  </Member>

</Type>

Generated mapping: Business.Mapping/Individual.xml

Note the <JoinedInheritance/> element that the wizard has filled in here in this mapping file. Again, the type discriminator value for Individual instances has to be specified, in this case: CustomerType.Individual. The specialised implementation of Name also has to be mapped. In this case, we compose the Name out of FirstName and LastName separated with a blank:

<Type name="Individual">

  <JoinedInheritance/>

  <Discriminator value="Individual"/>

  <CodeGeneratedProxy/>

 

  <Member name="Firstname">

    <PersistentField/>

  </Member>

  <Member name="Lastname">

           <PersistentField/>

  </Member>

  <Member name="Name" >

    <Linq>

      <![CDATA[

        String.Format("{0} {1}", FirstName, LastName)

      ]]>

    </Linq>

  </Member>

</Type>

Business.Mapping/Individual.xml with filled in type discriminator and specialised LINQ mapping for Name

Updating test data creation

Now that Customer is abstract in the database (no direct instances of Customer are allowed, as we removed the <CodeGeneratedProxy/> element from the mapping), we have to specialise the Customer instances created in CreateTestData() for Company or Individual. We can use the static factory wrapper methods provided in those classes:

static void CreateTestData()

{

  Helper.InitialiseDatabase();

  using (Context.Push(LocalContext.Create()))

  {

    Product[] products = {

        ...

                         };

 

    Individual.New(Helper.dd, "Jim", "Jones");

    Individual.New(Helper.dd, "Jane", "Doe");

    Individual.New(Helper.dd, "John", "Doe");

    Individual.New(Helper.dd, "Mary", "Jones");

    Company.New(Helper.dd, "Acme Inc");

 

    Context.CommitCurrent();

 

    ...

Updated CreateTestData() in Client/Program.cs specialising Customer instances created into Individual and Company

Note that subsequent code of CreateTestData() does not need to be modified, as it accesses Individual and Company instances through their generic Customer interface. Extent<Customer> will now return instances of Company and Individual when executed.



Relations in the class model