Paging in NHibernate
Lets say I have a domain model with a class called Blog that has a property called BlogEntries (that contains objects of type BlogEntry). If I have a database model with two tables "Blog" and "BlogEntry", it's not impossible that I have 1000 blog entries for a blog. If I were to show the blog on a web site, I would only want to display maybe 20 blog entries at a time, so I would have to use some sort of paging. I obviously don't want 1000 records to be fetched from the DB all the time.
How would I go about doing that? Should the BlogEntries property even be in the Blog domain object, or perhaps in the respository? I would still like to have the possibility to add blog entries as well as geting a paged result of existing ones. What would the NHibernate mapping look like?
The Blog/BlogE开发者_高级运维ntry thing is just an example, it could just as well have been a Customer/Order example or any other master/detail scenario.
I would make BlogEntry it's own Aggregate root, with it's own repository. You would get BlogEntry instances for a particular Blog by querying the BlogEntry repository for all BlogEntry's that have a given BlogID. I can't provide specifics about the repository beyond that since there are several different strategies for implementing repositories (one generic repository vs many, separate finder methods vs one that takes a complex specification object, etc). The finder method of the repository should support paging.
public class Blog
{
public int ID {get;set;}
// other stuff
}
public class BlogEntry
{
public int ID {get;set;}
public int BlogID {get;set;}
}
public class BlogEntryRepository
{
public IEnumerable<BlogEntry> FindByBlogID(
int blogID, int pageIndex, int pageSize)
{
// implementation
}
}
Alternatively, (also with BlogEntry modeled as an Aggregate root) you could add a collection of BlogEntryIDs to your Blog class. This would be better than having the BlogEntry instances themselves in the Blog class as you would have much less data to load when you want to get a Blog instance. With those IDs, you could select a subset of them and pass them into a BlogEntry repository method that accepts a collection of IDs. In other words there would be a little more logic in your domain to support the paging, and a more generic getter in the repository.
public class Blog
{
public int ID {get;set;}
public IEnumerable<int> BlogEntryIDs {get;set;}
// other stuff
}
public class BlogEntry
{
public int ID {get;set;}
public int BlogID {get;set;}
}
public class BlogEntryRepository
{
public IEnumerable<BlogEntry> Get(IEnumerable<int> blogEntryIDs)
{
// implementation
}
}
Usage for this approach would be like
// get the second page
var entries =
blogEntryRepo.Get(blog.BlogEntryIDs).Skip(1 * PAGE_SIZE).Take(PAGE_SIZE);
In the database, you would return multiple rows, one for each blog entry. (I also prefer to return multiple result sets to get all rows from all related tables in one database round trip. I also make use of SQL 2005's ROW_VERSION function to enable database paging.)
I generally prefer the second method, unless the typical usage shows prohibitive quantities of (e.g. more than a couple thousand) BlogEntry instances associated with a Blog. An array of too many int's would make me wary of performance and memory.
You have to access the blogentry in it's own repository to get a paged list.
For collection of blog entries you could use BatchSize attribute. It will let you not to select all collection from the data base, only required values.
精彩评论