NHibernate Mapping
See simple examples. Add the .hbm.xml files as "Embedded Resource". The number one error!
You can add xml intellisense by copying the nhibernate-mapping.xsd file to %ProgramFiles%/Microsoft Visual Studio 10.0\Xml\Schemas.
Mapping to tables
- The easiest is table per concrete class- like Linq2Sql
- For Table per class hierarchy (simple polymorphic entities) use a discriminator column in the sql table
Add <subclass ... discriminator-value="A"> ... <discriminator column="TYPE" />
You could also use sql: formula="case when TYPE is Null Then 'A' Else 'B' end" - Table per subclass- the base class is a table, and there are tables for each subclass whose primary key is a foreign key to the base table. NHibernate selects with an outer-join.
Use <joined-subclass table="SubTable"><key column="ID"/><property ...
Identity
generator can be:
- native (or identity or sequence)
- Integer auto-assigned primary keys - Identity columns in SQLServer, sequences in Oracle.
Entities are not just saved at Flush/Commit. - guid or guid.comb
- NHibernate assigns a guid (guid.comb uses datetime to make sequential ids).
- hilo
- Read unique keys from table (eg hibernate_unique_key).
- assigned
- Natural keys. New entities must be saved with ISession.Save, not SaveOrUpdate.
Composite keys - use <composite-id class="KeyClass"> <key-property ... (or key-many-to-one for FK-objects)
The KeyClass must omplement Equals, GetHashCode and be serializable.
The bags should specify <key> <column .../> <column ... in the same order.
Tables that have foreign keys/ many-to-one should also specify both columns.
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Northwind" assembly="Northwind">
<class name="OrderDetail" table="`Order Details`">
<composite-id name="Key" class="OrderDetailKey">
<key-property column="`OrderID`" name="OrderId" />
<key-property column="`ProductID`" name="ProductId" />
</composite-id>
<property name="UnitPrice" column="`UnitPrice`" type="System.Decimal" not-null="true" />
<property name="Quantity" column="`Quantity`" type="System.Int16" not-null="true" />
<property name="Discount" column="`Discount`" type="System.Single" not-null="true" />
</class>
</hibernate-mapping>
After the id, add a <version name="Version" column="VERSION"> (Version should be an int; it's not a database TIMESTAMP).
Alternatively use <class ... optimistic-lock="all" to update ..where col=origValue. Or, with care, optimistic-lock="dirty"/dynamic-update="true" or select-before-update="true".
Property
- The default access of "property" is fine usually, but "nosetter.camelcase-underscore" (set via the private field) is useful for primary keys and foreign key collections, and when you have business logic/validation in setters. To not use the getter, use "field.camelcase-underscore" (if you use "field" without a naming strategy, you have to map the _camelCaseNames).
- Escape column and table names with backtick.
- Add insert="false" updates="false" for immutable columns. On class, use dynamic-update="true" to just update changed columns (with optimistic-lock="dirty" for optimistic locks).
- Properties can be wrapped with <component name="Address" class="Address"> which map those properties to an aggregated class/ value type (it has no identity and cannot be persisted- the common example is an Address class consisting of lines of address).
- There are some special types:
Enums: specify the enum type or don't specify (reflection grabs the underlying enum type -eg Int32). Saves the underlying type.
Booleans: type="YesNo" maps "Y" and "N" strings in the database to boolean; TrueFalse is the same for T and F
For advanced stuff implement IUserType.
Collections- Foreign Keys
- Most foreign key relations can be mapped with <many-to-one> on one end and <bag>/<one-to-many> on the other marked with inverse="true". For large collections (100s of records or more- Product.Orders) don't do this.
.Net collection Mapping IEnumerable/ICollection/IList <bag name="Children">
<key column="fkId"/>
<one-to-many class="FKTable"/>
</bag>IList with order <list name="Children">
<key column="fkId"/>
<list-index column="Order"/>
<one-to-many class="FKTable"/>
</list>Iesi.Collections.Iset (no duplicates)
NB: no .Net 4 ISet, NH 3 is .Net 3.5 only<set> IDictionary <map> (Association table) <idBag> with it's own id
<collection-id column="AssocId" type="int"><generator class="guid-comb"/>... - Set the default namespace and asssmbly in the hibernate-mapping root, otherwise you'll have to specify all joined objects as "Namespace.ClassName, Assembly"
- If you use soft-deletes (DELETEFLAG=1), a "where" will filter those out. For ordered collections (orders by order date) consider "order-by"
- <many-to-one not-found="ignore" allows invalid keys (inconsistent databases). BUT NH automatically loads the related entity immediately (so you get an extra SQL statement). You get the same issue if you use "property-ref" (referencing a property rather than the primary key).
Simple association tables can be mapped as many-to-many (no class is needed for the association table).
<bag name="Categories" outer-join="true" table="PRODUCTCATEGORY" access="nosetter.camelcase-underscore">
<key column="ProductID"/>
<many-to-many column="CategoryID" class="Category"/>
</bag>