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?
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,
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).
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
In contrast to the reigning answer above, a clear point of opposition would be unit testing. Returning a simple generic IQueryable
is leaky abstraction to any client that has access to call it. There is no restriction for the repository method therefore, nothing to assert in a unit test.
The use of IQueryable
almost certainly depends on the context of the application itself, and the architecture. For example, if you're working on a personal project by yourself, you could safely assume the responsibility of wielding this unrestricted power without damning anyone but yourself. However, if you're working on an application for a company with code going into production, there is certainly more stakeholders than you involved. In this scenario, you could be working in the data access layer and attempting to code against other developer's use of your code. Now you need to control what comes in and goes out so that they don't break your code, and you don't break theirs. That's the incentive, and unit testing is the enforcement.
With that said, don't simply use IQueryable<T>
. Use what makes sense for your application.