18

I want to do paging with NHibernate when writing a Linq query. It's easy to do something like this:

return session.Query<Payment>()
    .OrderByDescending(payment => payment.Created)
    .Skip((page - 1)*pageSize)
    .Take(pageSize)
    .ToArray();

But with this I don't get any info about the total number of items. And if I just do a simple .Count(), that will generate a new call to the database.

I found this answer which solved it by using future. But it uses Criteria. How can I do this with Linq?

Community
  • 1
  • 1
Allrameest
  • 4,364
  • 2
  • 34
  • 50

4 Answers4

30

The difficulty with using Futures with LINQ is that operations like Count execute immediately.

As @vandalo found out, Count() after ToFuture() actually runs the Count in memory, which is bad.

The only way to get the count in a future LINQ query is to use GroupBy in an invariant field. A good choice would be something that is already part of your filters (like an "IsActive" property)

Here's an example assuming you have such a property in Payment:

//Create base query. Filters should be specified here.
var query = session.Query<Payment>().Where(x => x.IsActive == 1);
//Create a sorted, paged, future query,
//that will execute together with other statements
var futureResults = query.OrderByDescending(payment => payment.Created)
                         .Skip((page - 1) * pageSize)
                         .Take(pageSize)
                         .ToFuture();
//Create a Count future query based on the original one.
//The paged query will be sent to the server in the same roundtrip.
var futureCount = query.GroupBy(x => x.IsActive)
                       .Select(x => x.Count())
                       .ToFutureValue();
//Get the results.
var results = futureResults.ToArray();
var count = futureCount.Value;

Of course, the alternative is doing two roundtrips, which is not that bad anyway. You can still reuse the original IQueryable, which is useful when you want to do paging in a higher-level layer:

//Create base query. Filters should be specified here.
var query = session.Query<Payment>();
//Create a sorted, paged query,
var pagedQuery = query.OrderByDescending(payment => payment.Created)
                      .Skip((page - 1) * pageSize)
                      .Take(pageSize);
//Get the count from the original query
var count = query.Count();
//Get the results.
var results = pagedQuery.ToArray();

Update (2011-02-22): I wrote a blog post about this issue and a much better solution.

Diego Mijelshon
  • 52,548
  • 16
  • 116
  • 154
4

The following blog post has an implementation of ToFutureValue that works with LINQ.

http://sessionfactory.blogspot.com.br/2011/02/getting-row-count-with-future-linq.html

It has a small error on the following line that must be changed from this.

var provider = (NhQueryProvider)source.Provider;

To this:

var provider = (INhQueryProvider)source.Provider;

After apply the change you can use que queries in this way:

var query = session.Query<Foo>();
var futureCount = query.ToFutureValue(x => x.Count());
var page = query.Skip(pageIndex * pageSize).Take(pageSize).ToFuture();
Carlos Beppler
  • 2,552
  • 1
  • 18
  • 17
1
var query = Session.QueryOver<Payment>()
    .OrderByDescending(payment => payment.Created)
    .Skip((page -1 ) * pageSize)
    .Take(pageSize)

This is something I just discovered that the Linq to NH handles just fine, the ToRowCountQuery removes take/skip from the query and does a future row count.

var rowCount = query.ToRowCountQuery().FutureValue<int>();

var result = query.Future();

var asArray = result.ToArray();
var count = rowCount.Value();
genonist
  • 19
  • 1
  • Works well with SQL Server, but not with SQL Server Compact Edition... :( System.Data.SqlServerCe.SqlCeException : There was an error parsing the query. [ Token line number = 2,Token line offset = 1,Token in error = SELECT ] – Allrameest May 18 '11 at 08:16
  • 1
    The question specifically asked for a linq solution, not QueryOver. – Oskar Berggren Dec 17 '12 at 17:37
  • Strangely, ToRowCountQuery seems to remove groupings... http://stackoverflow.com/questions/8012966/torowcountquery-seems-to-ignore-groupings – Chris Haines Sep 18 '13 at 09:40
-6

Ok, it seems it should be working in your case, but I not tested:

return session.QueryOver<Payment>()
  .Skip((page - 1) * pageSize)
  .Take(pageSize)
  .SelectList(r => r.SelectCount(f => f.Id))
  .List<object[]>().First();

Test first before upvoting ;)

UPD: sorry, as I understand you now, you need to get Count of all items. Then you need to run the query without paging:

return session.QueryOver<Payment>()
  .SelectList(r => r.SelectCount(f => f.Id))
  .List<object[]>().First();
Genius
  • 1,784
  • 14
  • 12
  • Neither of those queries will do what is required. Get the paged set of object + get a count of the total number of objects that meet the criteria. – Neal Feb 10 '11 at 18:55
  • I think it is clearly mentioned in the question to use NHibernate + Linq instead of QueryOver. – Baig Mar 28 '13 at 05:56