NHibernate optimization - avoid unnecessary select queries

by andrei 23. September 2008 10:09

Let's consider the following structure of entities:

ScreenHunter_01 Sep. 23 22.23

On Product we have this mapping:

<bag name="ProductDetailCells" inverse="true" cascade="none" lazy="false">
    <key column="ProductId" />
    <one-to-many class="ProductDetailCell" />
</bag>

which means that I am always loading at least some of the cells.

Here is a quick test:

[Test]
public void normal_load_does_2_selects()
{
  Product p = Repository<Product>.LoadById(726);
}

and the result in SQL Server Profiler:

ScreenHunter_02 Sep. 23 22.42

It runs one select to get the Product and one select to get the ProductDetailCells for the Product. For 300 products we will obtain 301 database queries. If the ProductDetailCell table contains many rows, this will be a performance disaster as soon as we try to load a list of Products.

In my case I always needed to load a cell based on the type of column (ProductDetailColumnId), and I also knew that there is only one ProductDetailCell per ProductColumnType for each Product. So I tried to find a solution to avoid the performance hit.

Here is another test:

[Test]
public void optimized_load_does_1_select()
{
  Product p = new ProductRepository().InitialiseCriteriaFor(726)
    .WithProductDetailCellsOfType(enumProductDetailColumn.SubProductColumn)
    .LoadProduct();
}

I am using a fluent repository, like the one presented here.

Here is the WithProductDetailCellsOfType method:

public ProductRepository WithProductDetailCellsOfType(enumProductDetailColumn type)
{
  _criteria
    .CreateCriteria("ProductDetailCells", JoinType.LeftOuterJoin)
    .Add(Expression.Eq("Column.Id", (int)type));

  return this;
}

and here is the result in SQL Server Profiler:

ScreenHunter_03 Sep. 23 22.49

There is only one select, because this time it uses a left join with ProductDetailCell:

FROM dbo.Product this_ left outer join dbo.ProductDetailCell productdet1_ on this_.ProductId=productdet1_.ProductId

 

What I did is I used the knowledge that I always need ProductDetailCells based on the ProductDetailColumnType, and that there will be only one result each time. So I forced a left join instead of a separate select. This would not have worked if I had two ProductDetailCells per ProductDetailColumnType for each Product, because it would have doubled the returned rows. But in this case, it worked and it made a big difference.

This is only a punctual case, but the general idea is more important: always watch what your ORM is doing, at least until you learn it very well. Most of the times you can do the same thing in at least 2 different ways, and the ORM can't always make the best decisions on its own. It will get you what you want one way or the other, but sometimes you must say how.

 

 

Enjoy programming!

 

I am putting together a set of concept lists for the main programming techniques, which should help developers learning or using them to be more efficient. Here are the techniques in progress:

Please feel free to leave any comments or suggestions which could make these lists more useful for everyone.

 

 

Also, if you are interested in learning these techniques you can try the developer training modules.

Again, please feel free to leave any comments or suggestions which could make the training modules more useful for everyone. We are having great results with them at Akcedo (so they really work), but they can always be improved. Also, if you would like to use the modules or are already using them and you want to discuss about the process feel free to comment and ask questions here.

Comments

Add comment


(Will show your Gravatar icon)  

  Country flag

biuquote
  • Comment
  • Preview
Loading



Powered by BlogEngine.NET 1.4.5.0