Getting count with NHibernate + Linq + Future - linq

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?

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.

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();

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();

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();

Related

C# Entity Framework and Predicate Builder - Find the Index of a Matching Row within an IQueryable / Pagination Issue

I have a PredicateBuilder expression which returns an IQueryable like so (contrived example):
var predicate = PredicateBuilder.True<CoolEntity>();
predicate = predicate.And(x => x.id > 0);
IQueryable<CoolEntity> results = CoolEntityRepository
.FindBy(predicate)
.OrderByDescending(x => x.id);
I then use this to generate a paginated list containing only the results for a given page using an MVC Index controller for CoolEntity entities like so:
int total = results.Count(); //Used by my controller to show the total number of results
int pageNumber = 2;
int pagesize = 20;
//Only get the entities we need for page 2, where there are 20 results per page
List<CoolEntity> resultList = results
.Skip(pageNumber * pageSize)
.Take(pageSize)
.ToList()
This query ensures that only the relevant results are returned for each page, as there are many thousands of records returned from an SQL database via the repository. It has been working nicely.
The problem is, I now want to be able to find out which of the paginated pages for my Index I would need to return for a particular entity. For example, say one of my CoolEntity objects has an ID of 5 - how could I determine what the value for pageNumber would need to be if I wanted to load the paginated page that that entity would be on for my Index controller?
Specifically, how could I find out that for results, the entity with ID 5 was the 651st row, and consequently use this number to determine pageNumber (i.e. Math.Ceiling(651/pagesize) or something similar)? Is there a better approach to this?
A colleague of mine came up with a solution for this which seems to be very fast.
results
.Select( x => x.id)
.ToList()
.Select((entry, index) => new { ID = entry, Rank = index + 1, PageNumer = ((index+ 1) / pagesize ) + 1 })
.Where(x => x.id== 5)
This found the result from ~100000 records in about 00:00.191 seconds when tested in Linqpad. Please let me know if you have a more efficient way of doing this.

EF Code first - add collection to a collection, appropriate usage of skip() and take()

I have something like this:
var thread = _forumsDb.Threads
.Include("Posts")
.Single(t => t.Id == threadId);
Now, when i have a single thread, and a collection of posts within it, i want to count those posts, and then take some amount of them and remove the rest.
var count = thread.Posts.Count();
var tmp = thread.Posts.Skip(15).Take(15);
thread.Posts.Clear();
thread.Posts = tmp;
But that obviously doesn't work. So, how can i add collection to a collection? Is the thread.Posts.Clear(); appropriate here, or could i do it better?
In order to load only the 15 posts from the database you need and to perform only a single database query I would use a projection:
var data = _forumsDb.Threads
.Where(t => t.Id == threadId)
.Select(t => new
{
Thread = t,
Count = t.Posts.Count(),
Posts = t.Posts.OrderBy(p => p.SomeProperty).Take(15)
})
.Single();
var count = data.Count;
var thread = data.Thread;
Note that you need to order by some property if you want to use Take and Skip with LINQ to Entities. If in doubt just order by the post's Id.
If the relationship between Thread and Posts is one-to-many EF will populate the thread.Posts collection automatically with the 15 loaded posts.
This is how I would do it:
var count = thread.Posts.Count();
using (var tmp = thread.Posts.Skip(15).Take(15))
{
thread.Posts.Clear();
foreach (var Item in tmp)
thread.Posts.Add(Item);
}
You are skipping all the fifteen records which you are getting. Use below
Replace this
var tmp = thread.Posts.Skip(15).Take(15);
With
var tmp = thread.Posts.Skip(15).Take(30);
Hope this will help.

Truncating a collection using Linq query

I want to extract part of a collection to another collection.
I can easily do the same using a for loop, but my linq query is not working for the same.
I am a neophyte in Linq, so please help me correcting the query (if possible with explanation / beginners tutorial link)
Legacy way of doing :
Collection<string> testColl1 = new Collection<string> {"t1", "t2", "t3", "t4"};
Collection<string> testColl2 = new Collection<string>();
for (int i = 0; i < newLength; i++)
{
testColl2.Add(testColl1[i]);
}
Where testColl1 is the source & testColl2 is the desired truncated collection of count = newLength.
I have used the following linq queries, but none of them are working ...
var result = from t in testColl1 where t.Count() <= newLength select t;
var res = testColl1.Where(t => t.Count() <= newLength);
Use Enumerable.Take:
var testColl2 = testColl1.Take(newLength).ToList();
Note that there's a semantic difference between your for loop and the version using Take. The for loop will throw with IndexOutOfRangeException exception if there are less than newLength items in testColl1, whereas the Take version will silently ignore this fact and just return as many items up to newLength items.
The correct way is by using Take:
var result = testColl1.Take(newLength);
An equivalent way using Where is:
var result = testColl1.Where((i, item) => i < newLength);
These expressions will produce an IEnumerable, so you might also want to attach a .ToList() or .ToArray() at the end.
Both ways return one less item than your original implementation does because it is more natural (e.g. if newLength == 0 no items should be returned).
You could convert to for loop to something like this:
testColl1.Take(newLength)
Use Take:
var result = testColl1.Take(newLength);
This extension method returns the first N elements from the collection where N is the parameter you pass, in this case newLength.

EntityFramework: Add Where clause to ObjectSet Query to Select on CHILD attributes

The goal is to return a list of PARENT entities, based on attributes of their CHILD ENTITIES
eg Find me all the CASTLES where LADIES_IN_WAITING belong to PRINCESS 'X'
I want to do something like this:
var query = ObjectSet.Include(c => c.LADIES_IN_WAITING);
query = query.Where(p => p.REGION.ToLower().Contains("shrekVille"));
query = query.Where(p => p.LADIES_IN_WAITING.Where(c => c.PRINCESS.Equals("fiona")));
var results = query.ToList();
This is obviously the incorrect syntax but i can't find any clear examples of how to structure the Query.
I am currently resorting to something like this:
var query = ObjectSet.Include(c => c.LADIES_IN_WAITING);
query = query.Where(p => p.REGION.ToLower().Contains("shrekVille"));
// Get the results from the DB using the query built thus far
var results = query.ToList();
// Now filter the list in memory manually
foreach (var castle in results)
{
var matchingParents = new List<CASTLE>();
var matchingChildren = castle.LADIES_IN_WAITING.Where(a => a.PRINCESS.Equals("fiona"));
if (matchingChildren.Count() > 0)
matchingParents.Add(matchingChild);
}
results = matchingParents;
Any suggestions on how to correctly build the Query would be most welcomed!
You probably want to use the Any operator. It returns true if one item in a collection (i.e. 'any' of them) satisfies the predicate.
var query = ObjectSet.Include(c => c.LADIES_IN_WAITING);
query = query.Where(p => p.REGION.ToLower().Contains("shrekVille"));
// filter the query where, for each p,
// any of the LADIES_IN_WAITING have PRINCESS.Equals("fiona") == true
query = query.Where(p => p.LADIES_IN_WAITING.Any(c =>
c.PRINCESS.Equals("fiona"))); var results = query.ToList();
The complementary operator is All, which would filter your query to those results that have all the LADIES_IN_WAITING meeting the PRINCESS.Equals("fiona") criteria.

Groupby and where clause in Linq

I am a newbie to Linq. I am trying to write a linq query to get a min value from a set of records. I need to use groupby, where , select and min function in the same query but i am having issues when using group by clause. here is the query I wrote
var data =newTrips.groupby (x => x.TripPath.TripPathLink.Link.Road.Name)
.Where(x => x.TripPath.PathNumber == pathnum)
.Select(x => x.TripPath.TripPathLink.Link.Speed).Min();
I am not able to use group by and where together it keeps giving error .
My query should
Select all the values.
filter it through the where clause (pathnum).
Groupby the road Name
finally get the min value.
can some one tell me what i am doing wrong and how to achieve the desired result.
Thanks,
Pawan
It's a little tricky not knowing the relationships between the data, but I think (without trying it) that this should give you want you want -- the minimum speed per road by name. Note that it will result in a collection of anonymous objects with Name and Speed properties.
var data = newTrips.Where(x => x.TripPath.PathNumber == pathnum)
.Select(x => x.TripPath.TripPathLink.Link)
.GroupBy(x => x.Road.Name)
.Select(g => new { Name = g.Key, Speed = g.Min(l => l.Speed) } );
Since I think you want the Trip which has the minimum speed, rather than the speed, and I'm assuming a different data structure, I'll add to tvanfosson's answer:
var pathnum = 1;
var trips = from trip in newTrips
where trip.TripPath.PathNumber == pathnum
group trip by trip.TripPath.TripPathLink.Link.Road.Name into g
let minSpeed = g.Min(t => t.TripPath.TripPathLink.Link.Speed)
select new {
Name = g.Key,
Trip = g.Single(t => t.TripPath.TripPathLink.Link.Speed == minSpeed) };
foreach (var t in trips)
{
Console.WriteLine("Name = {0}, TripId = {1}", t.Name, t.Trip.TripId);
}

Resources