Mark Seemann has an excellent blog post about this subject: IQueryable is Tight Coupling. He sums it up nicely in the final part (emphasis mine):
You may think this is all a theoretical exercise, but it actually does matter. When writing Clean Code, it's important to design an API in such a way that it's clear what it does.
An interface like this makes false guarantees:
public interface IRepository
{
IQueryable<T> Query<T>();
}
According to the LSP and Postel's law, it would seem to guarantee that you can write any query expression (no matter how complex) against the returned instance, and it would always work.
In practice, this is never going to happen.
Programmers who define such interfaces invariably have a specific ORM in mind, and they implicitly tend to stay within the bounds they know are safe for that specific ORM. This is a leaky abstraction.
If you have a specific ORM in mind, then be explicit about it. Don't hide it behind an interface. It creates the illusion that you can replace one implementation with another. In practice, that's impossible. Imagine attempting to provide an implementation over an Event Store.
The cake is a lie.
ORM's like Entity Framework are implementations of the Repository and the Unit of Work pattern. There's no need to wrap them in another one.
new DbContext()
whenever they need and callSaveChanges()
whenever they want. It is not possible to test anything and only thing I can think of is additional layer hiding it.