I'm working on converting a CMS to use NHibernate. The CMS already has a domain model which uses multiple interface inheritance, where each interface is mapped to a database table. To explain what I'm doing, I've developed the following simplified model as an example:
Code:
public class Vehicle
{
public int VehicleID;
public int HorsePower;
}
public interface IProduct
{
public int ProductID;
// interface members must be properties
decimal Price { get; set; }
}
public class Car : Vehicle, IProduct
{
private decimal _price;
public decimal Price
{
get { return _price; }
set { _price = value; }
}
}
public class OrderLine
{
public IProduct Product;
}
In this model, there is a
Vehicle class, which has sub-class
Car. There are also
OrderLines, each of which holds a reference to a
Product (represented by the interface
IProduct).
Cars are also
Products, implementing the
IProduct interface. This model assumes that there are other
Vehicles which aren't
Products (e.g. CarTransporter), and that there are other
Products which aren't
Cars (e.g. Bananas), otherwise the model could be much simpler. For legibility the extra classes aren't shown.
N.b. I'm assuming "table-per-class" mapping is to be used.
The model would be easy to map if there were no Product table in the database. Unfortunately without a Product table the OrderLine table would (presumably?) have to have references into tables representing all subclasses of
IProduct (Car, Banana etc.) which it would coalesce when returning its
Product property. Using a Product table becomes even more desirable when storing collections of Products etc.
Conversely, when using a Product table we run into the problem that during mapping, the
Car class cannot be declared as a joined-subclass of both
Vehicle and
IProduct.
This seems like a no-win situation, but I've had a go at coming up with a workaround which doesn't affect the public interface presented by the domain model. The solution I've come up with is inelegant, any pointers or alternatives anyone can suggest would be greatly appreciated!
I've come up with the following database design:
The Vehicle-Car hierarchy is mapped using a relationship between their primary keys. I've used a foreign key to relate Product and Car, as using a relationship between the primary keys would mean having to ensure that any non-car
Products use IDs that aren't used by
Vehicles.
The mappings I've set up are as follows:
Code:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<!-- Vehicle -->
<class name="Vehicle" table="Vehicle">
<id name="VehicleID" column="VehicleID">
<generator class="identity" />
</id>
<property name="HorsePower" column="HorsePower" />
<!-- Car -->
<joined-subclass name="Car" table="Car">
<key column="CarID" />
</joined-subclass>
</class>
<!-- Order Line -->
<class name="OrderLine" table="OrderLine">
<id name="OrderLineID" column="OrderLineID">
<generator class="indentity" />
</id>
</class>
</hibernate-mapping>
Note the omission of a Product class. To cover products I've added an additional class, ProductReference:
Code:
public class ProductReference
{
public int ProductID
public Car Car;
// public Banana Banana;
public IProduct Item
{
get
{
if (Car != null)
return Car;
// else if (Banana != null)
// return Banana;
return null;
}
}
}
This class represents a reference to a Product and maps directly to the Product table in the database. The item property returns the appropriate sub-class, according to what product is pointed to by the reference. (Its role is quite similar to that of an abstract factory).
The mapping for this class is as follows:
Code:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<!-- ProductReference -->
<class name="ProductReference" table="Product">
<id name="ProductID" column="ProductID">
<generator class="identity" />
</id>
<one-to-one name="Car" class="Car" foreign-key="ProductID" />
<!--<one-to-one name="Banana" class="Banana" foreign-key="ProductID" />-->
</class>
</hibernate-mapping>
Using this class, I can add a private property/field to
OrderLine of type ProductReference and use a Hibernate mapping to instantiate this property using the relationship between the OrderLine and Product tables. The code would look something like this:
Code:
public class OrderLine
{
//...
// this field is set by an NHibernate mapping
private ProductReference _productReference;
public IProduct Product
{
get { return _productReference.Item; }
set { _productReference.Item = value; }
}
}
Code:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<!-- ... -->
<!-- Order Line -->
<class name="OrderLine" table="OrderLine">
<id name="OrderLineID" column="OrderLineID">
<generator class="indentity" />
</id>
<many-to-one name="ProductReference" column="ProductID" class="ProductReference" />
</class>
</hibernate-mapping>
As I said, any suggestions/help would be much appreciated!
Thanks for reading,
morcs