Search code examples
nhibernatesubclasscomposite-id

NHibernate subclasses and composite keys


I have a class StoreHours that has a composite key and has been working perfectly. A new demand came up for another type of hours to be returned. I thought "simple, I'll abstract the base class, have two concrete implementations and change my references in the app to one of the new classes". However, upon doing that, my unit tests failed with

X.Test.StoreTest.HoursTest: NHibernate.InstantiationException : Cannot instantiate abstract class or interface: X.Model.StoreHours

My mapping file looks like

<class name="StoreHours" table="StoreHour" abstract="true" discriminator-value="0" >
    <composite-id>
        <key-many-to-one name="Store"
            class="Store"
            column="StoreUid"/>
        <key-property name="DayOfWeek" 
            column="DayOfWeekId"
            type="System.DayOfWeek" />
    </composite-id>
    <discriminator column="StoreHourType" type="Byte" />
    <property name="OpenMinutes" column="OpenTime" />
    <property name="CloseMinutes" column="CloseTime" />
    <subclass name="OfficeHours" discriminator-value="1" />
    <subclass name="AccessHours" discriminator-value="2" />
</class>

I found someone with similar troubles here and started down their solution path but actually ended up with even more troubles than I started with.

I can persist the records to the database perfectly but onload, NHibernate is trying to instantiate the abstract 'StoreHours' even though I've only got a strongly type set off 'OfficeHours'

This seems like a really trivial requirement so I figure I must be doing something simple wrong. All hints appreciated.


Solution

  • The problem is in the way you are using the composite-id

    Table-per-class works with Composite-id, but only if the composite is implemented as a class

    so you need to create a class like

    public class StoreHoursCompositeId
     {
            public virtual Store Store { get; set; }
            public virtual DayOfWeek DayOfWeek { get; set; }
    
            // Implement GetHashCode(), is NH-mandatory
            // Implement Equals(object obj), is NH-mandatory
    }
    

    In your StoreHours object create a property which use the above class (in my example I called it "StoreHoursCompositeId")

    Your mapping become:

    <class name="StoreHours" table="StoreHour" abstract="true" discriminator-value="0" >
        <composite-id name="StoreHoursCompositeId" class="StoreHoursCompositeId">
            <key-many-to-one name="Store" class="Store"
                column="StoreUid"/>
            <key-property name="DayOfWeek" 
                column="DayOfWeekId"
                type="System.DayOfWeek" />
        </composite-id>
        <discriminator column="StoreHourType" type="Byte" />
        <property name="OpenMinutes" column="OpenTime" />
        <property name="CloseMinutes" column="CloseTime" />
        <subclass name="OfficeHours" discriminator-value="1" />
        <subclass name="AccessHours" discriminator-value="2" />
    </class>
    

    I had the very same problem and this fixed it for me.