Repository / IQueryable / Query Object
Asked Answered
P

1

19

I am building a repository and I've seen in many places 2 reasons not to expose IQueryable outside the repository.

  1. The first is because different LINQ providers could behave differently, and this difference should be contained within the repository.
  2. The second is to prevent service level developers from modifying the database query such that it accidentally causes performance issues.

I guess issue 2 can only be prevented by keeping all query logic within the repository and not allowing any form of external query building? But that does seem a bit impractical to me.

Issue 1 would seem to be resolved by using the Data Object Pattern.

e.g. public IEnumerable<T> FindBy(Query query)

My question is, why would I not just pass a lambda expression in, as that is provider independent, would appear to provide me with the same functionality as a query object, and the same level of separation?

e.g. public IEnumerable<T> FindBy(Expression<Func<T,bool>> predicate)

Is there any reason not to do this? Does it break some rules? Best-practises? that I should know about?

Pincince answered 15/8, 2012 at 5:36 Comment(2)
I have a repo system that works mostly off these finder lambda expressions and it works fine. One other thing I did was to expose a QuerySpec class that allows the user to spec the finder, the orderbys plus skip and take.Bandung
I'd be interested in your comments on my new question #11979640Pincince
M
27

Just return an IQueryable<T>.

Before you write another bit of "repository code", you will benefit significantly from reading Ayende's article Architecting in the Pit of Doom - The Evils of the Repository Abstraction Layer

Your approach is, without a doubt, adding significant unnecessary complexity.

All of the code from the other question at Generic List of OrderBy Lambda fails to do anything other than mask an existing effective API with an unnecessary and unfamiliar abstraction.

Regarding your two concerns,

  1. LINQ providers do behave differently but as long as the predicates that you are passing can be processed by the LINQ provider, this is irrelevant. Otherwise, you will still encounter the same issue, because you are passing in an Expression, which gets passed to the IQueryable eventually anyway. If the IQueryProvider implementation can't handle your predicate, then it can't handle your predicate. (You can always call a ToList() if you need to evaluate prior to further filtering that cannot be translated).

  2. Modifying a query can cause performance issues, but it is more likely to expose much needed functionality. Furthermore, the performance issues incurred by a sub-optimal LINQ query are likely to be significantly less detrimental than the performance issues incurred by pulling a lot more records than you need in order to avoid exposing an IQueryable or by systematically filtering any data access logic through bloated levels of abstractions that don't actually do anything (the first threat is more significant). In general, this won't be an issue because most leading LINQ providers will optimize your query logic in the translation process.

If you want to hide your query logic from the front end, then don't try making a generic repository. Encapsulate the queries with actual business specific methods. Now, I may be mistaken, but I am assuming your use of the repository pattern is inspired by Domain Driven Design. If this is the case, then the reason for using a repository is to allow you to create a persistence-ignorant domain with a primary focus on the domain model. However, using this kind of a generic repository doesn't do much more than change your semantics from Create Read Update Delete to Find Add Remove Save. There isn't any real business knowledge embedded there.

Consider the meaningfulness (and usability) of an

interface IPersonRepository 
{ 
     Person GetById(int id);
     IEnumerable<Person> FindByName(string firstName, string lastName);
}

in contrast to

interface IRepository<T> { 
     IEnumerable<T> FindBy(Query<T> query);
}

Furthermore, can you actually point to a benefit to using the IRepository<T> at all (as opposed to an IQueryable<T>)?

Also, consider that with the generic approach, you are not actually encapsulating query logic at all. You end up building it externally, which is going to lead to more additional unnecessary code.

*One other note about resources that advise against using IQueryable<T>, is that it is worthwhile to look at their publication date. There was a time when the availability of LINQ providers were pretty limited (to early EF and LINQ-to-SQL). At that time exposing an IQueryable<T> would entail incompatibility with some of Microsoft ORM's more popular substitutes (LINQ-to-NHibernate has long since been implemented). At this point in time, LINQ support is practically ubiquitous in serious ORM .NET libraries

Moody answered 16/8, 2012 at 3:20 Comment(3)
One benefit of using the Repository pattern would be that the datasource management (context, etc) could be encapsulated in the repository. Otherwise the client using the query would have to pass the context to the query itself, making the client "know too much". Also, the repository is nice for housing the context-specific details or write operations as well.Provencher
As developers we never should forget about the database, EF is not a magic bullet. If there will be a need to store UnitOfWork data in NoSQL or get data from SAP service, where will be IQueryable? Or what about optimisation with stored procedures? The article to think about it developersdev.blogspot.ru/2013/12/…Plyler
+1 for pointing out that, at least in a DDD context, the focus of a repository is the domain model. Understanding this should help reduce anxiety about including model-specific syntax (i.e. your "FindByName" example) in a repository implementation. In my experience it results in much more understandable code.Fy

© 2022 - 2024 — McMap. All rights reserved.