views:

38

answers:

1

This is for Entity Framework for .NET 3.5:

I have the need to query a table and include a collection of the "many" table of a one-to-many relationship. I'm trying to filter that collection as part of the query - I'm pretty new to Entity Framework, and I'm having trouble figuring it out.

Simplified example: Author has Books, and Book has an IsFiction column. I want a filtered list of authors, along with all fiction books.

Without the filter, it's easy:

var q = from a in db.Authors.Include("Books")
        where a.BirthYear > 1900
        select a;

I can filter after the fact, something like:

var fictionBooks = a.Books.Where(b => b.IsFiction);

But the problem is that the original query already ran, and included those results, which is unnecessary database processing.

I can query separately, like:

var q = from a in db.Authors where a.BirthYear > 1900 select a;
foreach (var a in q)
{
    var books = from b in db.Books 
                where ((b.Author.Id == a.Id) && (b.IsFiction))
                select b;
}

But of course that's one call for every author, which I want to avoid as well.

I can go backwards, like:

var allBooks = from b in db.Books.Include("Author")
               where b.IsFiction
               select b;

But then I'm back to the original problem, except now on the author side instead of the book side.

There must be a solution that encompasses everything - I can do it in SQL pretty easily:

select * from author a
left join book b on a.id = b.author_id and b.is_fiction = 1
where a.birth_year > 1900

Any suggestions?

+4  A: 

The forward way:

var q = from a in db.Authors.Include("Books")
        where a.BirthYear > 1900
        select new {
            Author = a,
            FictionBooks = a.Books.Where(b => b.IsFiction)
        };

Another way, derived from the SQL at the bottom of your question:

var q = from a in db.Authors
        from b in db.Books.Include("Author")
        where a.BirthYear > 1900 && b.IsFiction && a.Id == b.Author.Id
        select new { Author = a, Book = b };

The main difference between these two is that the first one will basically give you a collection of authors plus the list of fiction books for each author (which may be empty); while the second will give you a collection of author/books pairs (so it doesn’t return any authors with no fiction books).

Timwi
Thanks for the idea. I'm hoping to actually get a collection of Author objects if possible, not anonymous typed objects. On the same note, if I did take that anonymously typed object and enumerated the Author's Books collection, wouldn't it return the entire collection of books, not just the fiction ones?
Joe Enos
@Joe Enos: If you enumerated the `Author`’s `Books` collection, yes, of course. That’s why there’s the `FictionBooks` property :)
Timwi
If you are hoping to get `Author` objects whose `Books` collection is *explicitly incomplete* (because it’s filtered by fictionness), then this is not really how LINQ is intended to work. Each `Author` object returned by a LINQ query is intended to represent the `Author` entity in the DB, not an altered version of it.
Timwi
@Timwi: I should clarify - whether or not I specifically enumerate the books, wouldn't the database query return all books, not just fiction ones, since the LINQ query isn't really filtering the books in the main query? On of my goals is to reduce load from the database.Regarding your edit: That looks interesting - I'd have to do some manipulating of the objects when they come back, but it does look like the query might just be what I want. I'll have to give that a try.Thanks
Joe Enos
@Joe Enos: I have only actually used LINQ-to-SQL, but not LINQ-to-Entities, so I don’t know the details. [The difference between the two has already bitten me earlier today.](http://stackoverflow.com/questions/3539404/x/3539436#3539436) I recommend you look at the SQL query actually sent to the server if you want to be really sure what it does under the hood.
Timwi