Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NHibernate 3 paging and determining the total number of rows

Tags:

nhibernate

I have read somewhere (can't remeber where and how) that NHibernate 3 allows the determination of the total number of records whilst performing a paged query (in one database query). Is this right?

I have this code:

public IEnumerable<X> GetOrganisms(int PageSize, int Page, out int total)
{
    var query = (from e in Session.Query<X>() select e).AsQueryable();

    return query.Skip((Page - 1) * PageSize).Take(PageSize).ToList();
}

and would like to initialise 'total' as efficiently as possible.

Thanks.

Christian

PS:

Potential 'solution'?:

Total = (int) Session.CreateCriteria<X>()
.SetProjection(Projections.RowCount())
.FutureValue<Int32>().Value;

var query = (from e in Session.Query<X>() select e).AsQueryable();

return query.Skip((Page - 1) * PageSize).Take(PageSize).ToList();
like image 368
cs0815 Avatar asked Jul 20 '11 11:07

cs0815


3 Answers

I don't have enough reputation to comment on CodeProgression's solution above...but the proper ONE DB call using QueryOver w/ Future<> is:

var query = session.QueryOver<Organism>()
    .Skip((Page - 1) * PageSize)
    .Take(PageSize)
    .Future<Organism>();
// var result = query.ToList();
var rowcount = session.QueryOver<Organism>()
    .Select(Projections.Count(Projections.Id()))
    .FutureValue<int>().Value;
var result = query.ToList();
int iRowCount = rowcount.Value();

Once you execute the .ToList() - It will hit the database. So you'd have to hit the database again to get the rowCount...Which defeats the purpose of Future<>. Do your ToList() AFTER you've done all your .Future<> queries.

like image 154
Jeremy Walker Avatar answered Oct 30 '22 23:10

Jeremy Walker


Your potential solution will be handled in one transaction, but will be two db calls. If you must have only one db call, you should use a multiquery/future query as peer suggested. For more information on the future syntax, check out this post: http://ayende.com/blog/3979/nhibernate-futures.

Here are a few ways to accomplish your scenario...

QueryOver (2 db calls):
var query = session.QueryOver<Organism>();
var result = query
    .Skip((Page - 1) * PageSize)
    .Take(PageSize)
    .List();
var rowcount = query.RowCount();

With a sample set of 100 Organisms, and querying for the organisms 11-20, here are the two queries sent to the db:

SELECT TOP (@p0) Id0_0_, Title0_0_ FROM (SELECT this_.Id as Id0_0_, this_.Title as Title0_0_, ROW_NUMBER() OVER(ORDER BY CURRENT_TIMESTAMP) as __hibernate_sort_row FROM Organism this_) as query WHERE query.__hibernate_sort_row > @p1 ORDER BY query.__hibernate_sort_row;@p0 = 10 [Type: Int32 (0)], @p1 = 10 [Type: Int32 (0)]
SELECT count(*) as y0_ FROM Organism this_
QueryOver (1 db call with Future):
var query = session.QueryOver<Organism>()
    .Skip((Page - 1) * PageSize)
    .Take(PageSize)
    .Future<Organism>();
var result = query.ToList();
var rowcount = session.QueryOver<Organism>()
    .Select(Projections.Count(Projections.Id()))
    .FutureValue<int>().Value;

Querying for the same set of data as before, this is the query that is generated:

SELECT TOP (@p0) Id0_0_, Title0_0_ FROM (SELECT this_.Id as Id0_0_, this_.Title as Title0_0_, ROW_NUMBER() OVER(ORDER BY CURRENT_TIMESTAMP) as __hibernate_sort_row FROM Organism this_) as query WHERE query.__hibernate_sort_row > @p1 ORDER BY query.__hibernate_sort_row;SELECT count(this_.Id) as y0_ FROM Organism this_;;@p0 = 10 [Type: Int32 (0)], @p1 = 10 [Type: Int32 (0)]
Criteria(1 db call with Future):
var criteria = session.CreateCriteria<Organism>()
    .SetFirstResult((Page - 1) * PageSize)
    .SetMaxResults(PageSize)
    .Future<Organism>();
var countCriteria = session.CreateCriteria<Organism>()
    .SetProjection(Projections.Count(Projections.Id()))
    .FutureValue<int>().Value;

Again, querying for the same set of data, criteria with future results in the same query:

SELECT TOP (@p0) Id0_0_, Title0_0_ FROM (SELECT this_.Id as Id0_0_, this_.Title as Title0_0_, ROW_NUMBER() OVER(ORDER BY CURRENT_TIMESTAMP) as __hibernate_sort_row FROM Organism this_) as query WHERE query.__hibernate_sort_row > @p1 ORDER BY query.__hibernate_sort_row;SELECT count(this_.Id) as y0_ FROM Organism this_;;@p0 = 10 [Type: Int32 (0)], @p1 = 10 [Type: Int32 (0)]

Notice that all three query styles result in the same exact queries. The future syntax simply allows NHibernate to make one database call rather than two.

If you are using NHibernate 3, I think the most elegant way to handle this is using the new QueryOver syntax. (You used the old NHibernate.Linq syntax in your proposed solution. You are better off to learn the QueryOver syntax instead.)

like image 43
codeprogression Avatar answered Oct 31 '22 00:10

codeprogression


I do not think nhibernate 'realizes' the meaning of any query it performs, so determine the total number of rows is not standard queried.

The most efficient way to get the row count is with futures or a IMultiQuery (to get all results in one roundtript to the database)

nhibernate-futures

like image 39
Peter Avatar answered Oct 30 '22 23:10

Peter