dotnetjunkie/solidservices

Thoughts on Exposing IQueryable<T> from repository layer

Closed this issue · 2 comments

This question is a follow up to https://twitter.com/dot_NET_Junkie/status/1287991487440068608

I see code where IQueryable is exposed from Repository layer.

public interface IRepository
{
    IQueryable<T> Query<T>();
}

It is very handy in many cases, especially when using external libraries that leverage that interface, for example some plugins that sort/filter and bind to ui elements. (e.g https://hotchocolate.io/docs/10.0.0-rc.3/filters) at the same time, it looks like it is tightly coupled with the DB Backend only.

  1. Entity Framework itself seems to be an implementation of Repository and Unit of Work patterns. If we wrap it behind another repository is there any benefit other than the fact that it is decoupled from EF.

IQueryable<T> is a leaky abstraction. This makes it unsuited as an a construct to abstract away your data access layer.

When you query over an IQueryable, you instruct the underlying query provider to translate the LINQ expressions into the native query language (e.g. SQL), but the query provider's ability to do so differs immensely. In the past, I've experienced LINQ queries that executed correctly using EF, but failed with NHibernate, and vise versa. And don't get me started on LINQ queries that run in memory (e.g. LINQ to Objects). These queries just behave very differently, which means it gives very little guarantee when used inside a unit test. But there are differences in execution between SQLight and SQL as well; I could go on.

Long story short: you are opening a can of worms if you use IQueryable as an abstraction over your data access layer and trying to do the filtering in your Service/Business/Domain Layer. Since IRepository typically is meant as abstraction over your DAL, exposing IQueryable from a repository is typically not a good idea.

This all said, that doesn't mean, though, that having an abstraction that exposes IQueryable is always useless. It can be quite beneficial when used within the DAL, where you already have a strong dependency on a data source and possibly your OR/M tooling.

To give an example, I exposed IQueryable<T> from an abstraction within the DAL in an application I helped design in the past. I did this in order to apply row-based security in an effective and maintainable way. This way, query handlers (that were part of the DAL) could depend on that abstraction, which would return an IQueryable<T> with results filtered based on the region, permissions, and identity of the user. This made it impossible for query handlers to accidentally forget to apply filtering, which would limit the amount of security bugs and increased the trust users and testers had in the system.

Thanks. Well Thought and detailed answer as usual.