Sunday, November 4, 2012

NHibernate, Bidirectional vs Unidirectional associations

I heard from some peoples that bidirectional association is evil. I don’t know where them sneezed it from. I am not going to take a side and discuss it here. I am going to tell you that your choice have influence on NHibernate behavior and your code’s performance.
Lets start with bidirectional association.
Model
public class Order{
    public virtual Guid Id { get; set; }
    public virtual int Version { get; set; }
    public virtual DateTime OrderTime { get; set; }
    public virtual ICollection<OrderLine> OrderLines { get; set; }
}

public class OrderLine{
    public virtual Guid Id { get; set; }
    public virtual Order Order { get; set; }
    public virtual string ProductName { get; set; }
    public virtual decimal Price { get; set; }
    public virtual int Quantity { get; set; }
}

Order references a collection of order lines and each order line references it’s order. And I map collection as <bag> with inverse=true option.
Lets try to add a new order line to an existing order.

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
    var order = session.Get<Order>(id);
    order.OrderLines.Add(new OrderLine
    {
        Order = order,
        ProductName = "Product 1",
        Price = 9.99M,
        Quantity = 15
    });

    tx.Commit();
}

Take a look on what is going in the database

SELECT 
    order0_.Id as Id0_0_, 
    order0_.Version as Version0_0_, 
    order0_.OrderTime as OrderTime0_0_ 
FROM Orders order0_ 
WHERE order0_.Id=@p0

INSERT INTO OrderLines (OrderId, ProductName, Price, Quantity, Id) 
VALUES (@p0, @p1, @p2, @p3, @p4)

UPDATE Orders SET Version = @p0, OrderTime = @p1 WHERE Id = @p2 AND Version = @p3
Works as expected. The last query is for aggregate root versioning.
Now lets see what happens when we use model with unidirectional association. Little changes to code and mapping. And that’s what we got in the database.

SELECT 
    order2x0_.Id as Id0_0_, 
    order2x0_.Version as Version0_0_, 
    order2x0_.OrderTime as OrderTime0_0_ 
FROM Orders order2x0_ 
WHERE order2x0_.Id=@p0

SELECT 
    orderlines0_.OrderId as OrderId1_, 
    orderlines0_.Id as Id1_, 
    orderlines0_.Id as Id1_0_, 
    orderlines0_.ProductName as ProductN2_1_0_, 
    orderlines0_.Price as Price1_0_, 
    orderlines0_.Quantity as Quantity1_0_ 
FROM OrderLines orderlines0_ 
WHERE orderlines0_.OrderId=@p0

INSERT INTO OrderLines (ProductName, Price, Quantity, Id) 
VALUES (@p0, @p1, @p2, @p3)

UPDATE Orders SET Version = @p0, OrderTime = @p1 WHERE Id = @p2 AND Version = @p3

UPDATE OrderLines SET OrderId = @p0 WHERE Id = @p1

Dah!? What’s going on here?
The second query ignoring that collection mapped as a bag and goes to bring us already existing order lines. Seems like a bug.
Third query forgot to set reference from the new order line to the order.
And fifth query fixes that mistake. And that update will be executed for each order line.
It’s annoying. So, finally, we have at least two unnecessary queries when we decide to use unidirectional association.
I don’t know why and what was going in the heads of NH programmers when them coded it (no offence), but that is what we have and we will use it for the win! Just know your tools and make right decisions.

No comments:

Post a Comment