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.
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.