Alex Rivera | Logout

NHibernate IQueryable collection as property of root

Asked 2010-05-05T14:28:42.030
14

I have a root object that has a property that is a collection.

For example:

I have a Shelf object that has Books.

// Now
public class Shelf 
{
    public ICollection<Book> Books {get; set;}
}

// Want 
public class Shelf 
{
   public IQueryable<Book> Books {get;set;}
}

What I want to accomplish is to return a collection that is IQueryable so that I can run paging and filtering off of the collection directly from the the parent.

var shelf = shelfRepository.Get(1);

var filtered = from book in shelf.Books
               where book.Name == "The Great Gatsby"
               select book;

I want to have that query executed specifically by NHibernate and not a get all to load a whole collection and then parse it in memory (which is what currently happens when I use ICollection).

The reasoning behind this is that my collection could be huge, tens of thousands of records, and a get all query could bash my database.

I would like to do this implicitly so that when NHibernate sees an IQueryable on my class it knows what to do.

I have looked at NHibernate's LINQ provider and currently I am making the decision to take large collections and split them into their own repository so that I can make explicit calls for filtering and paging.

LINQ To SQL offers something similar to what I'm talking about.

Edit
Report

1 Answer

12

I've been trying to come up with a solution for a similar problem.

You can filter collections off an entity using ISession.FilterCollection. This creates an additional IQuery where you can count, page, add criteria, etc.

So, for example (my query in FilterCollection may be a little off, but you should get the idea):

ISession session = GetSession();
var shelf = session.Get<Shelf>(id);
var books = session.FilterCollection(shelf.Books, "where Name = :title").SetString("title", "The Great Gatsby").List<Book>();

There are a problem with that, however:

  1. The consumer executing the code needs to access ISession.CreateFilter, or you need to create a method on your repository that takes in a property, a query, and your query arguments (as well as any paging or other information). Not really the sexiest thing on the planet.
  2. It's not the LINQ you wanted.

Unfortunately, I don't think there's any way to get what you want out of the box with NHibernate. You could fake it, if you wanted to try, but they seem to fall flat to me:

Add a method or property that under the covers returns a LINQ to NHibernate IQueryable for this shelf:

public IQueryable<Book> FindBooks() {
  return Resolver.Get<ISession>().Linq<Book>().Where(b => b.Shelf == this);
}

where someone might consume that like this:

var shelf = ShelfRepo.Get(id);
var books = (from book shelf.FindBooks()
             where book.Title == "The Great Gatsby"
             select book);

Yuck! You are bleeding your persistence needs through your domain model! Maybe you could make it a little less worse by having a repository emit IQueryable, which at runtime is actually LINQ to NHibernate:

public IQueryable<Book> FindBooks() {
  return Resolver.Get<IRepository<Book>>().CreateQu
answered 2010-05-14T22:29:22.277

Your Answer