Chapter 6. Collection Mapping

6.1. Persistent Collections

NHibernate requires that persistent collection-valued fields be declared as a generic interface type, for example:

public class Product
{
    public virtual ISet<Part> Parts { get; set; } = new HashSet<Part>();

    public virtual string SerialNumber { get; set; }
}

The actual interface might be System.Collections.Generic.ICollection<T>, System.Collections.Generic.IList<T>, System.Collections.Generic.IDictionary<K, V>, System.Collections.Generic.ISet<T> or ... anything you like! (Where "anything you like" means you will have to write an implementation of NHibernate.UserType.IUserCollectionType.)

Notice how we initialized the instance variable with an instance of HashSet<T>. This is the best way to initialize collection valued properties of newly instantiated (non-persistent) instances. When you make the instance persistent - by calling Save(), for example - NHibernate will actually replace the HashSet<T> with an instance of NHibernate's own implementation of ISet<T>. Watch out for errors like this:

Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
...
ISet<Cat> kittens = new HashSet<Cat>();
kittens.Add(kitten);
cat.Kittens = kittens;
session.Save(cat);
kittens = cat.Kittens; //Okay, kittens collection is an ISet
HashSet<Cat> hs = (HashSet<Cat>) cat.Kittens; //Error!

Collection instances have the usual behavior of value types. They are automatically persisted when referenced by a persistent object and automatically deleted when unreferenced. If a collection is passed from one persistent object to another, its elements might be moved from one table to another. Two entities may not share a reference to the same collection instance. Due to the underlying relational model, collection-valued properties do not support null value semantics; NHibernate does not distinguish between a null collection reference and an empty collection.

You shouldn't have to worry much about any of this. Just use NHibernate's collections the same way you use ordinary .NET collections, but make sure you understand the semantics of bidirectional associations (discussed later) before using them.

Collection instances are distinguished in the database by a foreign key to the owning entity. The collection key is mapped by the <key> element.

Collections may contain almost any other NHibernate type, including all basic types, custom types, entity types and components. See Section 6.2.2, “Collection elements”.

All collection types except ISet and bag have an index column - a column that maps to an array or IList index or IDictionary key. See Section 6.2.3, “Indexed collections” for more information.

There are quite a range of mappings that can be generated for collections, covering many common relational models. We suggest you experiment with the schema generation tool to get a feeling for how various mapping declarations translate to database tables.

6.2. Mapping a Collection

The NHibernate mapping element used for mapping a collection depends upon the type of interface. By example, a <set> element is used for mapping properties of type ISet.

<class name="Product">
    <id name="SerialNumber" column="productSerialNumber"/>
    <set name="Parts">
        <key column="productSerialNumber" not-null="true"/>
        <one-to-many class="Part"/>
    </set>
</class>

Apart from <set>, there is also <list>, <map>, <bag>, <array> and <primitive-array> mapping elements. The <map> element is representative:

<map
    name="propertyName"                                         (1)
    table="tableName"                                           (2)
    schema="schemaName"                                         (3)
    catalog="catalogName"                                       (4)
    lazy="true|false|extra"                                     (5)
    inverse="true|false"                                        (6)
    cascade="all|none|save-update|delete|all-delete-orphan"     (7)
    sort="unsorted|natural|comparatorClass"                     (8)
    order-by="columnName asc|desc"                              (9)
    where="arbitrary sql where condition"                       (10)
    fetch="select|join|subselect"                               (11)
    batch-size="N"                                              (12)
    access="field|property|className"                           (13)
    optimistic-lock="true|false"                                (14)
    mutable="true|false"                                        (15)
    subselect="SQL expression"                                  (16)
    check="arbitrary sql check condition"                       (17)
    persister="persisterClass"                                  (18)
    collection-type="collectionClass"                           (19)
    outer-join="auto|true|false"                                (20)
    generic="true|false">                                       (21)

    <key ... />
    <map-key ... />
    <element ... />
</map>
(1)

name: the collection property name.

(2)

table (optional - defaults to property name): the name of the collection table. It is not used for one-to-many associations.

(3)

schema (optional): overrides the schema name specified by the root <hibernate-mapping> element.

(4)

catalog (optional): overrides the catalog name specified by the root <hibernate-mapping> element.

(5)

lazy (optional - defaults to true): may be used to disable lazy fetching and specify that the association is always eagerly fetched. Using extra fetches only the elements that are needed - see Section 21.1, “Fetching strategies” for more information.

(6)

inverse (optional - defaults to false): marks this collection as the "inverse" end of a bidirectional association.

(7)

cascade (optional - defaults to none): enables operations to cascade to child entities.

(8)

sort (optional): specifies a sorted collection with a natural sort order, or a given comparator class. See Section 6.3.1, “Sorted Collections”.

(9)

order-by (optional): specifies a table column (or columns) that defines the iteration order of the IDictionary, ISet or bag, together with an optional asc or desc.

(10)

where (optional): specifies an arbitrary SQL WHERE condition to be used when retrieving or removing the collection. It is useful if the collection should contain only a subset of the available data.

(11)

fetch (optional, defaults to select): chooses between outer-join fetching, fetching by sequential select, and fetching by sequential subselect - see Section 21.1.6, “Using subselect fetching”. join takes precedence over the lazy attribute and causes the association to be eagerly fetched.

(12)

batch-size (optional, defaults to 1): specifies a "batch size" for lazily fetching instances of this collection. See Section 21.1.5, “Using batch fetching”.

(13)

access (optional - defaults to property): the strategy NHibernate should use for accessing the property value.

(14)

optimistic-lock (optional - defaults to true): specifies that changes to the state of the collection results in increment of the owning entity's version. For one to many associations, you may want to disable this setting.

(15)

mutable (optional - defaults to true): a value of false specifies that the elements of the collection never change. This allows for minor performance optimization in some cases.

(16)

subselect (optional): maps an immutable and read-only collection to a database sub-select. This is useful if you want to have a view instead of a base table. It is not used for one-to-many associations.

(17)

check (optional): an SQL expression used to generate a multi-row check constraint for automatic schema generation. It is not used for one-to-many associations.

(18)

persister (optional>): specifies a custom ICollectionPersister.

(19)

collection-type (optional): the fully qualified type name of a class implementing IUserCollectionType.

(20)

outer-join (optional): This attribute is obsoleted in favor of fetch. auto is equivalent to not specifying fetch, true is equivalent to join and false is equivalent to select. It has no value matching the subselect fetch value.

(21)

generic (optional, defaults according to the reflected property type): if false, NHibernate will type the collection elements as object. Otherwise NHibernate will use reflection to determine the element type to use.

The mapping of an IList or array requires a separate table column holding the array or list index (the i in foo[i]). If your relational model doesn't have an index column, use an unordered ISet instead. This seems to put people off who assume that IList should just be a more convenient way of accessing an unordered collection. NHibernate collections strictly obey the actual semantics attached to the ISet, IList and IDictionary interfaces. IList elements don't just spontaneously rearrange themselves!

On the other hand, people who planned to use the IList to emulate bag semantics have a legitimate grievance here. A bag is an unordered, unindexed collection which may contain the same element multiple times. The .NET collections framework lacks an IBag interface, hence you have to emulate it with an IList. NHibernate lets you map properties of type IList or ICollection with the <bag> element. Note that bag semantics are not really part of the ICollection contract and they actually conflict with the semantics of the IList contract (however, you can sort the bag arbitrarily, discussed later in this chapter).

Note: large NHibernate bags mapped with inverse="false" are inefficient and should be avoided. NHibernate can't create, delete or update rows individually, because there is no key that may be used to identify an individual row.

6.2.1. Collection foreign keys

Collection instances are distinguished in the database by the foreign key of the entity that owns the collection. This foreign key is referred to as the collection key column, or columns, of the collection table. The collection key column is mapped by the <key> element.

There can be a nullability constraint on the foreign key column. For most collections, this is implied. For unidirectional one-to-many associations, the foreign key column is nullable by default, so you may need to specify not-null="true".

<key column="productSerialNumber" not-null="true"/>

The foreign key constraint can use ON DELETE CASCADE.

<key column="productSerialNumber" on-delete="cascade"/>

See Section 5.1.20, “key” for a full definition of the <key> element.

6.2.2. Collection elements

Collections can contain almost any other NHibernate type, including: basic types, custom types, components and references to other entities. This is an important distinction. An object in a collection might be handled with "value" semantics (its life cycle fully depends on the collection owner), or it might be a reference to another entity with its own life cycle. In the latter case, only the "link" between the two objects is considered to be a state held by the collection.

The contained type is referred to as the collection element type. Collection elements are mapped by <element> or <composite-element>, or in the case of entity references, with <one-to-many> or <many-to-many>. The first two map elements with value semantics, the next two are used to map entity associations. A last one exist for exotic entity associations, <many-to-any>, similar to the <any> mapping element.

6.2.3. Indexed collections

All collection mappings, except those with set and bag semantics, need an index column in the collection table. An index column is a column that maps to an array index, or IList index, or IDictionary key. The index of an IDictionary may be of any basic type, mapped with <map-key>. It can be an entity reference mapped with <map-key-many-to-many> (or in exotic cases <index-many-to-any>, similar to the <any> mapping element), or it can be a composite type mapped with <composite-map-key>. The index of an array or list is always of type Int32 and is mapped using the <list-index> element. The mapped column contains sequential integers that are numbered from zero by default.

<list-index
    column="columnName"                               (1)
    base="0|1|..."                                    (2)
/>
(1)

column (required): the name of the column holding the collection index values.

(2)

base (optional - defaults to 0): the value of the index column that corresponds to the first element of the list or array.

<map-key
    column="columnName"                               (1)
    formula="any SQL expression"                      (2)
    type="typeName"                                   (3)
    length="N"                                        (4)
/>
(1)

column (required unless formula is used): the name of the column holding the map key values.

(2)

formula (optional): a SQL formula used to evaluate the key of the map.

(3)

type (required): the type of the map keys.

(4)

length (optional): If the type takes a length and does not already specify it, its length.

<map-key-many-to-many
    column="columnName"                               (1)
    formula="any SQL expression"                      (2)
    class="className"                                 (3)
    entity-name="entityName"                          (4)
    foreign-key="foreignKeyName"                      (5)
/>
(1)

column (required unless formula is used): the name of the column holding the map key values.

(2)

formula (optional): a SQL formula used to evaluate the key of the map.

(3)

class (required): the entity class used as the map key.

(4)

entity-name (optional): the entity name of the associated class.

(5)

foreign-key (optional): specifies the name of the foreign key constraint for DDL generation.

<composite-map-key
    class="className">                                (1)

    <key-property .../>                               (2)
    <key-many-to-one .../>                            (3)
    ...
/>
(1)

class (required): the component class used as the map key. Make sure you override GetHashCode() and Equals() correctly on the component class.

(2)

<key-property>: maps a component property.

(3)

<key-many-to-one>: maps a component many-to-one association.

If your table does not have an index column, and you still wish to use IList as the property type, you can map the property as a NHibernate <bag>. A bag does not retain its order when it is retrieved from the database, but it can be optionally sorted or ordered. Also consider using an ISet as the property type.

The <index>, <index-many-to-many> and <composite-index> elements are obsoleted predecessors to <map-key>/<list-index>, <map-key-many-to-many> and <composite-map-key> respectively.

6.2.4. Collections of Values and Many-To-Many Associations

Any collection of values or many-to-many associations requires a dedicated collection table with a foreign key column or columns, collection element column or columns, and possibly an index column or columns.

For a collection of values, use the <element> tag.

<element
    column="columnName"                               (1)
    formula="any SQL expression"                      (2)
    type="typeName"                                   (3)
    length="L"                                        (4)
    precision="P"                                     (5)
    scale="S"                                         (6)
    not-null="true|false"                             (7)
    unique="true|false"                               (8)
/>
(1)

column (required unless formula is used): the name of the column holding the collection element values.

(2)

formula (optional): an SQL formula used to evaluate the element.

(3)

type (required): the type of the collection element.

(4)

length (optional): if the type takes a length and does not already specify it, its length.

(5)

precision (optional): if the type takes a precision and does not already specify it, its precision.

(6)

scale (optional): if the type takes a scale and does not already specify it, its scale.

(7)

not-null (optional - defaults to false): sets the column nullability for DDL generation.

(8)

unique (optional - defaults to false): sets the column uniqueness for DDL generation.

A many-to-many association is specified using the <many-to-many> element.

<many-to-many
    column="columnName"                                    (1)
    formula="any SQL expression"                           (2)
    class="className"                                      (3)
    fetch="join|select"                                    (4)
    lazy="false|proxy"                                     (5)
    not-found="ignore|exception"                           (6)
    entity-name="entityName"                               (7)
    property-ref="propertyNameFromAssociatedClass"         (8)
    order-by="columnName asc|desc"                         (9)
    where="arbitrary sql where condition"                  (10)
    unique="true|false"                                    (11)
    foreign-key="foreignKeyName"                           (12)
    outer-join="auto|true|false"                           (13)
/>
(1)

column (required unless formula is used): the name of the element foreign key column.

(2)

formula (optional): an SQL formula used to evaluate the element foreign key value.

(3)

class (required): the name of the associated class.

(4)

fetch (optional, defaults to join): enables outer-join or sequential select fetching for this association. This is a special case; for full eager fetching (in a single SELECT) of an entity and its many-to-many relationships to other entities, you would enable join fetching not only of the collection itself, but also with this attribute on the <many-to-many> nested element.

(5)

lazy (optional, defaults to proxy): may be used to disable lazy fetching and specify that the associated entities are always eagerly fetched. This is a special case; disabling laziness of the collection itself only would only eagerly load the list of ids of the associated entities. For fully loading the associated entities instead, while keeping fetch value as select, you would also disable laziness with this attribute on the <many-to-many> nested element.

(6)

not-found (optional - defaults to exception): specifies how foreign keys that reference missing rows will be handled. ignore will treat a missing row as a null association.

(7)

entity-name (optional): the entity name of the associated class.

(8)

property-ref (optional): the name of a property of the associated class that is joined to this foreign key. If not specified, the primary key of the associated class is used.

(9)

order-by (optional): specifies a table column (or columns) that defines the iteration order of the collection. This is a special case; the order-by on the collection could order only by the foreign key. The order-by on the <many-to-many> nested element allows to order by the associated entity columns.

(10)

where (optional): specifies an arbitrary SQL WHERE condition to be used when retrieving or removing the collection. This is a special case; the where on the collection could filter only by the foreign key. The where on the <many-to-many> nested element allows to filter by the associated entity columns.

(11)

unique (optional): enables the DDL generation of a unique constraint for the foreign-key column. This makes the association multiplicity effectively one-to-many.

(12)

foreign-key (optional): specifies the name of the foreign key constraint for DDL generation.

(13)

outer-join (optional): This attribute is obsoleted in favor of fetch. auto is equivalent to not specifying fetch, true is equivalent to join and false is equivalent to select.

Here are some examples.

A set of strings:

<set name="Names" table="NAMES">
    <key column="GROUPID"/>
    <element column="NAME" type="String"/>
</set>

A bag containing integers with an iteration order determined by the order-by attribute:

<bag name="Sizes" table="SIZES" order-by="SIZE ASC">
    <key column="OWNER"/>
    <element column="SIZE" type="Int32"/>
</bag>

An array of entities, in this case, a many-to-many association (note that the entities are lifecycle objects, cascade="all", see Section 10.10, “Lifecycles and object graphs”):

<array name="Addresses" table="PersonAddress" cascade="all">
    <key column="personId"/>
    <list-index column="sortOrder"/>
    <many-to-many column="addressId" class="Address"/>
</array>

A map from string indices to dates:

<map name="Holidays" table="holidays" schema="dbo" order-by="hol_name asc">
    <key column="id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date" type="Date"/>
</map>

A list of components (discussed in Chapter 8, Component Mapping):

<list name="CarComponents" table="car_components">
    <key column="car_id"/>
    <list-index column="sortOrder"/>
    <composite-element class="Eg.Car.CarComponent">
        <property name="Price"/>
        <property name="Type"/>
        <property name="SerialNumber" column="serial_no"/>
    </composite-element>
</list>

6.2.5. One-To-Many Associations

A one-to-many association links the tables of two classes via a foreign key with no intervening collection table. This mapping loses certain semantics of normal .NET collections:

  • No null values may be contained in a dictionary, set or list (unless using not-found value ignore).

  • An instance of the contained entity class cannot belong to more than one instance of the collection.

  • An instance of the contained entity class cannot appear at more than one value of the collection index.

An association from Product to Part requires the existence of a foreign key column and possibly an index column into the Part table. A <one-to-many> tag indicates that this is a one-to-many association.

<one-to-many
    class="className"                                      (1)
    not-found="ignore|exception"                           (2)
    entity-name="entityName"                               (3)
/>
(1)

class (required): the name of the associated class.

(2)

not-found (optional - defaults to exception): specifies how foreign keys that reference missing rows will be handled. ignore will treat a missing row as a null association.

(3)

entity-name (optional): the entity name of the associated class.

The <one-to-many> element does not need to declare any columns. Nor is it necessary to specify the table name anywhere.

Very Important Note: If the foreign key column of an <one-to-many> association is declared NOT NULL, you must declare the <key> mapping not-null="true" or use a bidirectional association with the collection mapping marked inverse="true". See Section 6.3.2, “Bidirectional Associations” for more information.

Example:

<set name="Parts">
    <key column="product_id"/>
    <one-to-many class="Eg.Part, Eg"/>
</set>

6.3. Advanced collection mappings

6.3.1. Sorted Collections

NHibernate supports collections implemented by System.Collections.Generic.SortedList<T> and System.Collections.Generic.SortedSet<T>. You must specify a comparer in the mapping file:

<set name="Aliases" table="person_aliases" sort="natural">
    <key column="person"/>
    <element column="name" type="String"/>
</set>

<map name="Holidays" sort="My.Custom.HolidayComparer, MyAssembly">
    <key column="year_id"/>
    <map-key column="hol_name" type="String"/>
    <element column="hol_date" type="Date"/>
</map>

Allowed values of the sort attribute are unsorted, natural and the name of a class implementing System.Collections.Generic.IComparer<T>.

If you want the database itself to order the collection elements use the order-by attribute of set, bag or map mappings. This performs the ordering in the SQL query, not in memory.

Setting the order-by attribute tells NHibernate to use Iesi.Collections.Generic.LinkedHashSet class internally for sets, maintaining the order of the elements. It is not supported on maps, unless specifying also a custom collection-type implementing IUserCollectionType.

<set name="Aliases" table="person_aliases" order-by="name asc">
    <key column="person"/>
    <element column="name" type="String"/>
</set>

<map name="Holidays" order-by="hol_date, hol_name" collection-type="...">
    <key column="year_id"/>
    <map-key column="hol_name" type="String"/>
    <element column="hol_date type="Date"/>
</map>

Note that the value of the order-by attribute is an SQL ordering, not a HQL ordering!

Associations can even be sorted by some arbitrary criteria at runtime using a CreateFilter().

sortedUsers = s
    .CreateFilter(group.Users, "order by this.Name")
    .List<User>();

6.3.2. Bidirectional Associations

A bidirectional association allows navigation from both "ends" of the association. Two kinds of bidirectional association are supported:

one-to-many

set or bag valued at one end, single-valued at the other

many-to-many

set or bag valued at both ends

You can specify a bidirectional many-to-many association simply by mapping two many-to-many associations to the same database table and declaring one end as inverse. You cannot select an indexed collection.

Here is an example of a bidirectional many-to-many association that illustrates how each category can have many items and each item can be in many categories:

<class name="Category">
    <id name="Id" column="CATEGORY_ID"/>
    ...
    <bag name="Items" table="CATEGORY_ITEM">
        <key column="CATEGORY_ID"/>
        <many-to-many class="Item" column="ITEM_ID"/>
    </bag>
</class>

<class name="Item">
    <id name="Id" column="ITEM_ID"/>
    ...

    <!-- inverse end -->
    <bag name="Categories" table="CATEGORY_ITEM" inverse="true">
        <key column="ITEM_ID"/>
        <many-to-many class="Category" column="CATEGORY_ID"/>
    </bag>
</class>

Changes made only to the inverse end of the association are not persisted. This means that NHibernate has two representations in memory for every bidirectional association, one link from A to B and another link from B to A. This is easier to understand if you think about the .NET object model and how we create a many-to-many relationship in C#:

category.Items.Add(item);      // The category now "knows" about the relationship
item.Categories.Add(category); // The item now "knows" about the relationship

session.Update(item);          // No effect, nothing will be saved!
session.Update(category);      // The relationship will be saved

The non-inverse side is used to save the in-memory representation to the database. We would get an unnecessary INSERT/UPDATE and probably even a foreign key violation if both would trigger changes! The same is of course also true for bidirectional one-to-many associations.

You can map a bidirectional one-to-many association by mapping a one-to-many association to the same table column(s) as a many-to-one association and declaring the many-valued end inverse="true".

<class name="Parent">
    <id name="Id" column="id"/>
    ...
    <set name="Children" inverse="true">
        <key column="parent_id"/>
        <one-to-many class="Child"/>
    </set>
</class>

<class name="Child">
    <id name="Id" column="id"/>
    ...
    <many-to-one name="Parent" class="Parent" column="parent_id"/>
</class>

Mapping one end of an association with inverse="true" doesn't affect the operation of cascades as these are orthogonal concepts.

6.3.3. Bidirectional associations with indexed collections

A bidirectional association where one end is represented as a <list> or <map>, requires special consideration. If there is a property of the child class that maps to the index column you can use inverse="true" on the collection mapping:

<class name="Parent">
    <id name="Id" column="parent_id"/>
    ...
    <map name="Children" inverse="true">
        <key column="parent_id"/>
        <map-key column="name"
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>

<class name="Child">
    <id name="Id" column="child_id"/>
    ...
    <property name="Name" column="name"
        not-null="true"/>
    <many-to-one name="Parent"
        class="Parent"
        column="parent_id"
        not-null="true"/>
</class>

If there is no such property on the child class, the association cannot be considered truly bidirectional. That is, there is information available at one end of the association that is not available at the other end. In this case, you cannot map the collection inverse="true". Instead, you could use the following mapping:

<class name="Parent">
    <id name="Id" column="parent_id"/>
    ...
    <map name="Children">
        <key column="parent_id"
            not-null="true"/>
        <map-key column="name"
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>

<class name="Child">
    <id name="Id" column="child_id"/>
    ...
    <many-to-one name="Parent"
        class="Parent"
        column="parent_id"
        insert="false"
        update="false"
        not-null="true"/>
</class>

Note that in this mapping, the collection-valued end of the association is responsible for updates to the foreign key.

6.3.4. Ternary Associations

There are three possible approaches to mapping a ternary association. One approach is to use an IDictionary with an association as its index:

<map name="Contracts">
    <key column="employer_id"/>
    <map-key-many-to-many column="employee_id" class="Employee"/>
    <one-to-many class="Contract"/>
</map>
<map name="Connections">
    <key column="incoming_node_id"/>
    <map-key-many-to-many column="outgoing_node_id" class="Node"/>
    <many-to-many column="connection_id" class="Connection"/>
</map>

A second approach is to remodel the association as an entity class. This is the most common approach.

A final alternative is to use composite elements, see Section 8.2, “Collections of dependent objects”.

6.3.5. Using an <idbag>

If you've fully embraced our view that composite keys are a bad thing and that entities should have synthetic identifiers (surrogate keys), then you might find it a bit odd that the many to many associations and collections of values that we've shown so far all map to tables with composite keys! Now, this point is quite arguable; a pure association table doesn't seem to benefit much from a surrogate key (though a collection of composite values might). Nevertheless, NHibernate provides a feature that allows you to map many-to-many associations and collections of values to a table with a surrogate key.

The <idbag> element lets you map a List (or Collection) with bag semantics.

<idbag name="Lovers" table="LOVERS">
    <collection-id column="ID" type="Int64">
        <generator class="hilo"/>
    </collection-id>
    <key column="PERSON1"/>
    <many-to-many column="PERSON2" class="Person" fetch="join"/>
</idbag>

An <idbag> has a synthetic id generator, just like an entity class. A different surrogate key is assigned to each collection row. NHibernate does not, however, provide any mechanism to discover the surrogate key value of a particular row.

The update performance of an <idbag> supersedes a regular <bag>. NHibernate can locate individual rows efficiently and update or delete them individually, similar to a <list>, <map> or <set>.

As of version 2.0, the native identifier generation strategy is supported for <idbag> collection identifiers.

6.3.6. Heterogeneous Associations

The <many-to-any> and <index-many-to-any> elements provide for true heterogeneous associations. These mapping elements work in the same way than the <any> element - and should also be used rarely, if ever.

6.4. Lazy Initialization

Collections (other than arrays) may be lazily initialized, meaning they load their state from the database only when the application needs to access it. Initialization happens transparently to the user so the application would not normally need to worry about this (in fact, transparent lazy initialization is the main reason why NHibernate needs its own collection implementations). However, if the application tries something like this:

IDictionary<string, int> permissions;
using (s = sessions.OpenSession())
using (ITransaction tx = sessions.BeginTransaction())
{
    var u = s.Load<User>(userId);
    permissions = u.Permissions;
    tx.Commit();
}

int accessLevel = permissions["accounts"];  // Error!

It could be in for a nasty surprise. Since the permissions collection was not initialized when the ISession was committed, the collection will never be able to load its state. The fix is to move the line that reads from the collection to just before the commit. (There are other more advanced ways to solve this problem, however.)

Alternatively, use a non-lazy collection. However, it is intended that lazy initialization be used for almost all collections, especially for collections of entities (for reasons of efficiency).

Exceptions that occur while lazily initializing a collection are wrapped in a LazyInitializationException.

Disable lazy initialization of collection by using the optional lazy attribute:

<set name="Names" table="NAMES" lazy="false">
    <key column="group_id"/>
    <element column="NAME" type="String"/>
</set>

In some application architectures, particularly where the code that accesses data using NHibernate, and the code that uses it are in different application layers, it can be a problem to ensure that the ISession is open when a collection is initialized. There are two basic ways to deal with this issue:

  • In a web-based application, an event handler can be used to close the ISession only at the very end of a user request, once the rendering of the view is complete. Of course, this places heavy demands upon the correctness of the exception handling of your application infrastructure. It is vitally important that the ISession is closed and the transaction ended before returning to the user, even when an exception occurs during rendering of the view. The event handler has to be able to access the ISession for this approach. We recommend that the current ISession is stored in the HttpContext.Items collection (see chapter 1, Section 1.4, “Playing with cats”, for an example implementation).

  • In an application with a separate business tier, the business logic must "prepare" all collections that will be needed by the web tier before returning. This means that the business tier should load all the data and return all the data already initialized to the presentation/web tier that is required for a particular use case. Usually, the application calls NHibernateUtil.Initialize() for each collection that will be needed in the web tier (this call must occur before the session is closed) or retrieves the collection eagerly using a NHibernate query with a FETCH clause.

  • You may also attach a previously loaded object to a new ISession with Update() or Lock() before accessing uninitialized collections (or other proxies). NHibernate can not do this automatically, as it would introduce ad hoc transaction semantics!

You can use the CreateFilter() method of the NHibernate ISession API to get the size of a collection without initializing it:

var count = s
    .CreateFilter(collection, "select count(*)")
    .UniqueResult<long>();

CreateFilter() is also used to efficiently retrieve subsets of a collection without needing to initialize the whole collection.

6.5. Collection examples

This section covers collection examples.

The following class has a collection of Child instances:

using System;
using System.Collections.Generic;

namespace Eg

    public class Parent
    {
        public virtual long Id { get; set; }

        public virtual ISet<Child> Children { get; set; }

        ...
    }
}

If each child has at most one parent, the most natural mapping is a one-to-many association:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    assembly="Eg" namespace="Eg">

    <class name="Parent">
        <id name="Id">
            <generator class="sequence"/>
        </id>
        <set name="Children">
            <key column="parent_id"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="Id">
            <generator class="sequence"/>
        </id>
        <property name="Name"/>
    </class>

</hibernate-mapping>

This maps to the following table definitions:

create table parent (Id bigint not null primary key)
create table child (Id bigint not null primary key, Name varchar(255), parent_id bigint)
alter table child add constraint childfk0 (parent_id) references parent

The Child class has no property referencing its parent.

If the parent is required, use a bidirectional one-to-many association:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    assembly="Eg" namespace="Eg">

    <class name="Parent">
        <id name="Id">
            <generator class="sequence"/>
        </id>
        <set name="Children" inverse="true">
            <key column="parent_id"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="Id">
            <generator class="sequence"/>
        </id>
        <property name="Name"/>
        <many-to-one name="Parent" class="Parent" column="parent_id" not-null="true"/>
    </class>

</hibernate-mapping>

Notice the NOT NULL constraint:

create table parent ( Id bigint not null primary key )
create table child ( Id bigint not null
                     primary key,
                     Name varchar(255),
                     parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parent

Here, the Child class has a Parent property.

Alternatively, if this association must be unidirectional you can declare the NOT NULL constraint on the <key> mapping:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    assembly="Eg" namespace="Eg">

    <class name="Parent">
        <id name="Id">
            <generator class="sequence"/>
        </id>
        <set name="Children">
            <key column="parent_id" not-null="true"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="Id">
            <generator class="sequence"/>
        </id>
        <property name="Name"/>
    </class>

</hibernate-mapping>

On the other hand, if a child might have multiple parents, a many-to-many association is appropriate:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    assembly="Eg" namespace="Eg">

    <class name="Parent">
        <id name="Id">
            <generator class="sequence"/>
        </id>
        <set name="Children" table="childset">
            <key column="parent_id"/>
            <many-to-many class="Child" column="child_id"/>
        </set>
    </class>

    <class name="eg.Child">
        <id name="Id">
            <generator class="sequence"/>
        </id>
        <property name="Name"/>
    </class>

</hibernate-mapping>

Table definitions:

create table parent ( Id bigint not null primary key )
create table child ( Id bigint not null primary key, name varchar(255) )
create table childset ( parent_id bigint not null,
                        child_id bigint not null,
                        primary key ( parent_id, child_id ) )
alter table childset add constraint childsetfk0 (parent_id) references parent
alter table childset add constraint childsetfk1 (child_id) references child

For more examples and a complete explanation of a parent/child relationship mapping, see Chapter 7, Association Mappings and Chapter 23, Example: Parent/Child.