I always go with repository interfaces.
First reason is testability - DbContext
and DbSet<T>
are hard to mock. So, unit-testing of services becomes really hard, if even possible.
Second reason is IQueryable<T>
being exposed by EF. It makes data-access logic spread over whole application, which violates SRP and makes fixing issues harder.
Third reason might be not very important - ability to switch interface implementations - not only with real object and mock, but with different data-access framework. You always think - "no, I definitely will stick with EF". But in real world I have experience of switching to Dapper due to performance issues and ease of development. And once I had experience of switching from in-memory data storage to EF. So, it's always nice to have that option instead of depending on framework-specific API.
Fourth reason is nice domain-specific API which repositories give you. Instead of having something like db.Orders.Where(o => o.Category == "Food")
you have nice method repository.GetOrdersByCategory
.
Another reason is that you have data-access logic in one place. No duplication. In sample above you can have five places in your application which use same query to filter orders by category. With repository you will have single method for that, which will be called in five places. Consider what if your business rules will change? E.g. you will need also to check if category is active.
Also keep in mind - once you have UoW and Repository base classes and interfaces, you can reuse them in different projects.