Chapter 8. Component Mapping

The notion of a component is re-used in several different contexts, for different purposes, throughout NHibernate.

8.1. Dependent objects

A component is a contained object that is persisted as a value type and not an entity reference. The term "component" refers to the object-oriented notion of composition and not to architecture-level components. For example, you can model a person like this:

public class Person
{
    public virtual string Key { get; set; }

    public virtual DateTime Birthday { get; set; }

    public virtual Name Name { get; set; }

    ...
}
public class Name
{
    public string First { get; set; }

    public string Last { get; set; }

    public char Initial { get; set; }
}

Now Name may be persisted as a component of Person. Name defines getter and setter methods for its persistent properties, but it does not need to declare any interfaces or identifier properties.

Our NHibernate mapping would look like:

<class name="Eg.Person, Eg" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid.hex"/>
    </id>
    <property name="Birthday" type="date"/>
    <component name="Name" class="Eg.Name, Eg"> <!-- class attribute optional -->
        <property name="Initial"/>
        <property name="First"/>
        <property name="Last"/>
    </component>
</class>

The person table would have the columns pid, Birthday, Initial, First and Last.

Like value types, components do not support shared references. In other words, two persons could have the same name, but the two person objects would contain two independent name objects that were only "the same" by value. The null value semantics of a component are ad hoc. When reloading the containing object, NHibernate will assume that if all component columns are null, then the entire component is null. This is suitable for most purposes.

The properties of a component can be of any NHibernate type (collections, many-to-one associations, other components, etc). Nested components should not be considered an exotic usage. NHibernate is intended to support a fine-grained object model.

The <component> element allows a <parent> sub-element that maps a property of the component class as a reference back to the containing entity.

<class name="Eg.Person, Eg" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid.hex"/>
    </id>
    <property name="Birthday" type="date"/>
    <component name="Name" class="Eg.Name, Eg">
        <parent name="NamedPerson"/> <!-- reference back to the Person -->
        <property name="Initial"/>
        <property name="First"/>
        <property name="Last"/>
    </component>
</class>

8.2. Collections of dependent objects

Collections of components are supported (eg. an array of type Name). Declare your component collection by replacing the <element> tag with a <composite-element> tag.

<set name="SomeNames" table="some_names">
    <key column="id"/>
    <composite-element class="Eg.Name, Eg"> <!-- class attribute required -->
        <property name="Initial"/>
        <property name="First"/>
        <property name="Last"/>
    </composite-element>
</set>

Note: if you define an ISet of composite elements, it is very important to implement Equals() and GetHashCode() correctly.

Composite elements can contain components but not collections. If your composite element contains components, use the <nested-composite-element> tag. This case is a collection of components which themselves have components. You may want to consider if a one-to-many association is more appropriate. Remodel the composite element as an entity, but be aware that even though the .Net model is the same, the relational model and persistence semantics are still slightly different.

A special case of a composite element is a composite element with a nested <many-to-one> element. This mapping allows you to map extra columns of a many-to-many association table to the composite element class. The following is a many-to-many association from Order to Item where PurchaseDate, Price and Quantity are properties of the association:

<class name="Order" ... >
    ...
    <set name="PurchasedItems" table="purchase_items">
        <key column="order_id">
        <composite-element class="Purchase">
            <property name="PurchaseDate"/>
            <property name="Price"/>
            <property name="Quantity"/>
            <many-to-one name="Item" class="Item"/> <!-- class attribute is optional -->
        </composite-element>
    </set>
</class>

There cannot be a reference to the purchase on the other side for bidirectional association navigation. Components are value types and do not allow shared references. A single Purchase can be in the set of an Order, but it cannot be referenced by the Item at the same time.

Even ternary (or quaternary, etc) associations are possible:

<class name="Order" ... >
    ...
    <set name="PurchasedItems" table="purchase_items">
        <key column="order_id">
        <composite-element class="OrderLine">
            <many-to-one name="PurchaseDetails class="Purchase"/>
            <many-to-one name="Item" class="Item"/>
        </composite-element>
    </set>
</class>

Composite elements can appear in queries using the same syntax as associations to other entities.

8.3. Components as IDictionary indices

The <composite-map-key> element lets you map a component class as the key of an IDictionary. Make sure you override GetHashCode() and Equals() correctly on the component class. See Section 6.2.3, “Indexed collections” for more information on the <composite-map-key> element.

8.4. Components as composite identifiers

You can use a component as an identifier of an entity class. Your component class must satisfy certain requirements:

  • It must be marked with the Serializable attribute.

  • It must re-implement Equals() and GetHashCode(), consistently with the database's notion of composite key equality.

  • It should re-implement ToString() if you consider using the second level cache. See Section 27.1, “How to use a cache?”.

You cannot use an IIdentifierGenerator to generate composite keys. Instead the application must assign its own identifiers.

Since a composite identifier must be assigned to the object before saving it, you cannot use unsaved-value of the identifier to distinguish between newly instantiated instances and instances saved in a previous session. See Section 5.1.5.7, “Assigned Identifiers” for more information.

Use the <composite-id> tag, with nested <key-property> or <key-many-to-one> elements, in place of the usual <id> declaration. For example, the following OrderLine class has a primary key that depends upon the (composite) primary key of Order.

<class name="OrderLine">
    <composite-id name="Id" class="OrderLineId">
        <key-property name="lineId"/>
        <key-property name="orderId"/>
        <key-property name="customerId"/>
    </composite-id>

    <property name="Name"/>

    <many-to-one name="Order" class="Order"
            insert="false" update="false">
        <column name="orderId"/>
        <column name="customerId"/>
    </many-to-one>
    ...
</class>

Any foreign keys referencing the OrderLine table are now composite. Declare this in your mappings for other classes. An association to OrderLine is mapped like this:

<many-to-one name="OrderLine" class="OrderLine">
<!-- the "class" attribute is optional, as usual -->
    <column name="lineId"/>
    <column name="orderId"/>
    <column name="customerId"/>
</many-to-one>

The <column> element is an alternative to the column attribute everywhere. Using the <column> element is required for composite keys, but also gives more declaration options, which are mostly useful when using hbm2ddl. See Section 5.1.21, “column and formula elements”.

A many-to-many association to OrderLine also uses the composite foreign key:

<set name="UndeliveredOrderLines">
    <key column name="warehouseId"/>
    <many-to-many class="OrderLine">
        <column name="lineId"/>
        <column name="orderId"/>
        <column name="customerId"/>
    </many-to-many>
</set>

The collection of OrderLine in Order would use:

<set name="OrderLines" inverse="true">
    <key>
        <column name="orderId"/>
        <column name="customerId"/>
    </key>
    <one-to-many class="OrderLine"/>
</set>

The <one-to-many> element declares no columns.

If OrderLine itself owns a collection, it also has a composite foreign key.

<class name="OrderLine">
    ...
    <list name="DeliveryAttempts">
        <key>   <!-- a collection inherits the composite key type -->
            <column name="lineId"/>
            <column name="orderId"/>
            <column name="customerId"/>
        </key>
        <list-index column="attemptId" base="1"/>
        <composite-element class="DeliveryAttempt">
            ...
        </composite-element>
    </set>
</class>

8.5. Dynamic components

You can also map a property of type IDictionary or IDictionary<string, object>, or declared as a C# dynamic:

<dynamic-component name="UserAttributes">
    <property name="Foo" column="FOO"/>
    <property name="Bar" column="BAR"/>
    <many-to-one name="Baz" class="Baz" column="BAZ_ID"/>
</dynamic-component>

The semantics of a <dynamic-component> mapping are identical to <component>. The advantage of this kind of mapping is the ability to determine the actual properties of the component at deployment time, just by editing the mapping document. Runtime manipulation of the mapping document is also possible, using a DOM parser. You can also access, and change, NHibernate's configuration-time metamodel via the Configuration object.