+8  A: 

I have been thinking about this a lot lately, after starting at my current job. I am used to Repositories, they go the full IQueryable path using just bare bones repositories as you suggest.

I feel the repo pattern is sound and does a semi-effective job at describing how you want to work with the data in the application domain. However the issue you are describing definitely occurs. It gets messy, fast, beyond a simple application.

Are there, perhaps, ways to rethink why you are asking for the data in so many ways? If not, I really feel that a hybrid approach is the best way to go. Create repo methods for the stuff you reuse. Stuff that actually it makes sense for. DRY and all that. But those one-offs? Why not take advantage of IQueryable and the sexy things you can do with it? It is silly, as you said, to create a method for that, but it doesn't mean you don't need the data. DRY doesn't really apply there does it?

It would take discipline to do this well, but I really think it's an appropriate path.

Chad Ruppert
A: 

@Alex - i know this is an old question, but what I would be doing would be letting the Repository do really simple stuff only. This means, get all records for a table or view.

Then, in the SERVICES layer (you are using an n-tiered solution, right? :) ) i would be handling all the 'special' query stuff there.

Ok, example time.

Repository Layer

ContactRepository.cs

public IQueryable<Contact> GetContacts()
{
    return (from q in SqlContext.Contacts
            select q).AsQueryable();
}

Nice and simple. SqlContext is the instance of your EF Context .. which has an Entity on it called Contacts .. which is basically your sql Contacts class.

This means, that method basically is doing: SELECT * FROM CONTACTS ... but it's not hitting the database with that query .. it's only a query right now.

Ok .. next layer.. KICK ... up we go (Inception anyone?)

Services Layer

ContactService.cs

public  ICollection<Contact> FindContacts(string name)
{
    return FindContacts(name, null)
}

public ICollection<Contact> FindContacts(string name, int? year)
{
   IQueryable<Contact> query = _contactRepository.GetContacts();

   if (!string.IsNullOrEmpty(name))
   {
       query = from q in query
               where q.FirstName.StartsWith(name)
               select q;
   }

   if (int.HasValue)
   {
       query = from q in query
               where q.Birthday.Year <= year.Value
               select q);
    }

    return (from q in query
            select q).ToList();
}

Done.

So lets recap. First, we start our with a simple 'Get everything from contacts' query. Now, if we have a name provided, lets add a filter to filter all contacts by name. Next, if we have a year provided, then we filter the birthday by Year. Etc. Finally, we then hit the DB (with this modified query) and see what results we get back.

NOTES:-

  • I've omitted any Dependency Injection for simplicity. It's more than highly recommended.
  • This is all pseduo-code. Untested (against a compiler) but you get the idea ....

Takeaway points

  • The Services layer handles all the smarts. That is where you decide what data you require.
  • The Repository is a simple SELECT * FROM TABLE or a simple INSERT/UPDATE into TABLE.

Good luck :)

Pure.Krome
I think it is worth mentioning that this really only works efficiently, and please correct me if I am wrong, if you are using a DAL that supports deferred execution such as Linq To Sql as per your example. Otherwise you will retrieve a lot of data from your data store that may not get used. Now if you know the user is going to be using the same data set in multiple different ways this ultimately may be ok, but if they are just running this query once then retrieving entirely unrelated data this design would lead to mentionable overhead.
joshlrogers
We've all done this hack. For each additional filter we add an 'optional' parameter. I think the authors original example is far cleaner and easier to use (and reuse).
Jerod Houghtelling
@joshlrogers : dude - waaaay incorrect! DO you know what an IQueryable is/does? it doesn't hit the DB. It's a _query_. So i'm not really doing a SELECT * FROM XXX. I *extend* that query by adding extra where clauses .. when required. So the final sql is actually a SELECT * FROM XXX WHERE blah (if the user has provided the *optional* name and/or *optional* year arguments*. So unless I misunderstood you .. careful what you say :(
Pure.Krome
@Jerod Houghtelling : hack? How is this a hack? With the author's original example, they will have a very complex repository (as he/she suggested).. and then this is sorta-replicated in the services layer. Not very DRY if you ask me. Want a more indepth example of my answer? Check this good blog post out: http://huyrua.wordpress.com/2010/07/13/entity-framework-4-poco-repository-and-specification-pattern/
Pure.Krome
@Pure.Krome That is exactly what I am saying. Your code example used execution deferred code. Linq isn't executed until it is actually called so at that point everything creates one coherent query. I was only bringing this up because if they try to home brew something they should know that it isn't good practice to get an entire table of data then perform all filtering, sorting, etc, afterwards except in rare circumstances. I was just trying to make sure people understood what was happening.
joshlrogers
@Joshlrogers : i'm so confused :( Are you saying that my code is (1) using deferred execution and (2) returns all rows to code (massive overhead) then client code does filtering/sorting or (3) returns only the exact rows required because filtering/sorting is part of the sql code executed? (My answers are (1) and (3) ... definately not (2).
Pure.Krome
@Pure.Krome : Sorry I didn't mean to be confusing. Your code is deferred execution because you are utilizing linq. No you aren't experiencing 2 due to the nature of deferred execution the sql isn't generated until the linq query is actually executed with a ToList(). I wasn't trying to say your code was wrong at all, quite the contrary. What I was trying to say to anyone reading your post that doesn't happen to be using an ORM or DAL that supports deferred execution that they would be doing # 2 if they followed your instructions.
joshlrogers
Gotcha now :) and yep - agreed. But i didn't worry about that 'cause this was an EF question :)
Pure.Krome