Sunday, 20 June 2010

Learn NHibernate Lesson 5 - Modelling foreign key relationships

disclaimer - at the moment, this post is in note format from a workshop I did, I need to it clean up, and add grammar

5. Modelling foreign key relationships

a one to many relationship between customer and orders in a database could be said to be, or coded like:[csharp]

class Customer {
private IList<Order> _orders;

class Order {
private Customer customer;

[/csharp]


sql may be:[sql]

from parent to child:
select * from customer inner join order on c.customerid on o.customerid where c.customerid = (id of the customer)

from child to parent:
select * from order inner join customer on o.customerid on c.customerid where o.orderid = (id of the order)

[/sql]


mapping files

supported relationships

one-to-one
one-to-many
many-to-one
many-to-many

not all orm software support all these, especially many to many

mappping files also control cascade updates and cascade deletes

you can control navigation between parents and children in mapping files
NHibernate collection types
Bag

collection of objects where each element can repeat (implemented as an IList or IList of <T>) { 1, 2, 3, 3, 3 }
Set

collection of object where each element must be unique (same as a true mathematical set) (implemented as an ISet or ISet<T>)

ISet is in Iesi.Collections.dll (not in .Net libraries)
List

collection of object with integer indicies (impleneted as an ArrayList or List<T>
Map

collection of key value pairs (implemented as IDictionary or hashtable)
Collection Mappings

defines:
collection type (bag, set..)
cardinality (one to many etc..)
cascasde behaviour
who is responsible for updating (inverse = true, child is responsible)

Child collection mapping define very little, usually just a pointer to support navigation back to the parent and the cardinality back to the parent

deep object graphs casue a lot of trouble, recursive relationships due to self referetial modelling (orders on customer and customer on orders)
Lazy Loading

the name doesnt exaplin exactly whats happenning
it should be named 'delayed laoding' or 'load on demand'

implentation is The Proxy Pattern

how it works
orders colletion on customers would create a proxy object, so it doesnt actually go and get the orders, but it knows how to. so if we dont interact with the orders collection, it will never be loaded. as soon as we do, the proxy object will be invoked and it will go off tothe dband get the orders..

after that, the proxy object goes away, the colection will now point to the correct orders object
the same thing is true in the reverse direction, each order object on the collection will contain proxy objects to the customer. however this will not actually go to the db as nhibe will check the session fo rthe customer we need, and it will already be there as the parent that loaded the orders collection

our order class willl look like this: //excuse the lack of settters and getters this is just an example[csharp]

public class Order {
public int orderid;
public DateTIme Order;

//now, although we are only referencing the customer and an int, or id in the database, we do not model this as an int, customerid, we actually model it as a Customer object

public Customer customer;

//in our customers class we now have

public ISet<Order> _orders;

[/csharp]

mapping the order class

now, in our customer hbm.xml mapping file, we add the following:[xml]

<set name="Orders" table="`Order`" generic="true" inverse="true"> //inverse=true the child object is responsible for maintaining the relationship back to the customer
<key column="Customer" />
<one-to-many class="DataTransfer.Order, DataTransfer" />
</set>

[/xml]


now, we create a new Order hbm.xml Mapping file..[xml]

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembley="DataTransfer" namespace="DataTransfer">

<class name="DataTransfer.Order, DataTransfer" table="`Order`"> (Namepsapce.TypeName, Namespace)

<id name="OrderId" column="OrderId" type="Int32" unsaved-value="0">
<generator></generator>
</id>

<property name="Name" column="Name" type="string" length="50" not-null="false"></property>
<property name="OrderDate" column="OrderDate" type="DateTime" not-null="true"></property>

<many-to-one name="Customer" column="Customer" not-null="true" class="DataTransfer.Customer, DataTransfer" />
</class>
</hibernate-mapping>

[/xml]


remember to change the build action for new hbm.xml files to embedded resource..

NOTE, in NHibe, if you are using db keywords, like table or order, instead of using [table] or [order] we use the back tick like so: `Order`
Testing the mapping files

understanding lazy loading

looking at a GetCustomerById()

if we look at the orders in the loaded customer, we'll get an nhibe exception, no session, why? beacsue we have wrapped our session in a using statment
so when the object tries to hydrate the orders object, there is no session

so for lazy loading you must make sure you have not disposed of the session before the lazy loading has worked

you could change the lazy attribute in the set tag in customers mapping file to false : lazy="false"

you could make another method called get customers and orders by custoemr id that does not dispose of the session

we will look at better ways of handling session lifecycle in later lessons

now if we make the new method[csharp]

public Customer GetCustomersAndOrdersByCustomerId(int id)
{
Customer customer = session.Get<Customer>(id);

//fill out (load) the orders and dont load as proxy
NHibernateUtil.Initialize(customer.Orders); // util is in iesi.collections.dll

}

[/csharp]