Showing posts with label NHibernate. Show all posts
Showing posts with label NHibernate. Show all posts

Sunday, November 13, 2011

NHibernate bi-directional - use it carefully (Part II)

In the previous post I've showed you one reason to be very careful when using bi-directional associations in NHibernate.

In this post I'll show you another reason. Again, for the example, I will use Customer with many Orders.

We will create a one-to-many association from Customer to Orders:
<set name="Orders" lazy="true" cascade="all-delete-orphan">
      <key column="CUSTOMER_ID" />
      <one-to-many class="Order" not-found="ignore"/>
</set>

and we will create a many-to-one from Order to Customer:
<many-to-one name="Customer" class="Customer" column="CUSTOMER_ID"/>
Now consider the following scenario:
someCustomer.Orders.Add(someNewOrder);

...query the db using HQL

...

someNewOrder.OrderDate = DateTime.Now;

....

CustomerRepository.SaveOrUpdate(someCustomer);
transaction.Commit();

If you'll take a look at table 'ORDERS' right after the commit, you'll find out that the CUSTOMER_ID field in the new record created for someNewOrder is... NULL.

And why is that?
let's see what NHibernate is doing a step by step:
1 - Right before querying the DB by using HQL, NHibernate is flushing all the changes to the DB, and that includes creating a new record for someNewOrder At this stage the record in the DB is perfectly well.

2 - By changing someNewOrder.OrderDate NHIbernate considers someNewOrder as 'dirty' and it will flushed it to the DB later on when the transaction will be commited.

3 - Finally, when the transaction is commited, again NHibernate is flushing all the changes but this time it's generating an 'UPDATE' statement for someNewOrder since it's no longer a new object - it has a record in the DB.

And here is the problem - as we saw in the last step, an 'UPDATE' statement for someNewOrder
is executed. But since someNewOrder.Customer is null (nowhere in the code we've ever changed it) - the 'UPDATE' statement looks something like this:
"UPDATE ORDERS SET ...CUSTOMER_ID=NULL... WHERE...".

OK, now you have a few options to solve it:
1 - do not use HQL - use session.Get() instead (only if you can...). This doesn't enforce NHibernate to flush like HQL does.

2 - You can make sure that someNewOrder.Customer actually does hold the Customer before committing.

3 - You can go to Order.hbm.xml and add update="false" to the many-to-one association. like this:
<many-to-one name="Customer" class="Customer" column="CUSTOMER_ID 
update="false"/>
this tells NHibernate not to include the Customer when generating an 'UPDATE' statement for an Order.
But this also means that you can not update the Order's Customer like this:
someOrder.Customer = someCustomer;
OrderRepository.SaveOrUpdate(someOrder);
it won't work if you chose option No. 3...

Thursday, November 10, 2011

NHibernate bi-directional - use it carefully (Part I)

A few days ago we've experienced a very strange behavior with NHibernate. Or so we thought... 
For the demonstration i will use a Customer that has many Orders. 

And so we did that:
customer.Orders.Remove(order);
CustomerRepository.SaveOrUpdate(customer);

The "strange" behavior was that the Customer itself has been deleted from the DB along with all of its other Orders.

the mappings were like this:

Customer.hbm.xml:
<set name="Orders" lazy="true" cascade="all-delete-orphan">
     <key column="CUSTOMER_ID" />
     <one-to-many class="Order" not-found="ignore" />
</set>


Order.hbm.xml:
<many-to-one name="Customer" class="Customer" column="CUSTOMER_ID" cascade="all-delete-orphan"/>


So, what went wrong? Why did NHibernate delete the customer when all we did was removing one order? the reason was:

When we removed one Order from the Customer.Orders list, NHibernate deleted this Order from the DB since we have cascade="all-delete-orphan" on Customer.Orders. 
But while deleting an Order, NHibernate also is deleting its Customer. Why? Because we have 
cascade="all-delete-orphan" on Order.Customer, meaning - we actually tell NHibernate to delete a Customer whenever one of its Orders is being deleted. And that's usually a stupid thing to do.



Tuesday, November 1, 2011

Strongly Typed HQL


If you're using NHibernate, you have 3 main options to execute a query against the DB: Criteria API, HQL and SQL. The last one won't be relevant to our discussion.

For a long time, the names of the properties were a weakness point since they were represented by strings. For example:

var crit = Session.CreateCriteria(typeof(Person));
crit.Add(NHibernate.Criterion.Expression.Eq("ID", someVal));

Lately, Criteria API has an extension to support Lambda expressions and therefore can be strongly typed:

var crit = Session.CreateCriteria(typeof(Person));
crit.Add<Person>(p => p.ID == someVal)

But in some complicated cases - HQL is inevitable, and since HQL is represented by string - refactoring can be very painful. For example:

string hql = "select p from Person p where p.ID=:someVal"

To avoid this problem, I've created 2 methods. To use these methods, simply locate them in the base class of all the classes that build HQL quries. In our case it was BaseRepository which is the base class for all the Repositories (if you're following DDD). These 2 methods are:

Class<T>();
Prop<T>(Expression<Func<T, object>> property);

I will show the implementation soon, but first an example:

string hql = "select p from " 
+ Class<Person>() + " p where p." + Prop<Person>(x => x.ID) + "=:someVal"

And the implementation:

public string Class<T>()
{
    return typeof(T).ToString();
}

public string Prop<T>(Expression<Func<T, object>> property)
{
    if (property.Body.NodeType == ExpressionType.MemberAccess)
    {
        return (property.Body as MemberExpression).Member.Name;
    }
    else if (property.Body.NodeType == ExpressionType.Convert
        && (property.Body as UnaryExpression).Operand.NodeType == ExpressionType.MemberAccess)
{
        return ((property.Body as UnaryExpression).Operand as MemberExpression).Member.Name;
}

    return "";
}