There are several good blogs about how to implement the repository pattern together with the unit-of-work pattern using generic classes.
Implementing a Data Access L
One of the reasons we use the repository pattern is to encapsulate fat queries. These queries make it hard to read, understand and test actions in ASP.NET MVC controllers. Also, as your application grows, the chances of you repeating a fat query in multiple places increases. With the repository pattern, we encapsulate these queries inside repository classes. The result is slimmer, cleaner, more maintainable and easier-to-test actions. Consider this example:
var orders = context.Orders
.Include(o => o.Details)
.ThenInclude(d => d.Product)
.Where(o => o.CustomerId == 1234);
Here we are directly using a DbContext without the repository pattern. When your repository methods return IQueryable, someone else is going to get that IQueryable and compose a query on top of it. Here’s the result:
var orders = repository.GetOrders()
.Include(o => o.Details)
.ThenInclude(d => d.Product)
.Where(o => o.CustomerId == 1234);
Can you see the difference between these two code snippets? The only difference is in the first line. In the first example, we use context.Orders, in the second we use repository.GetOrders(). So, what problem is this repository solving? Nothing!
Your repositories should return domain objects. So, the GetOrders() method should return an IEnumerable. With this, the second example can be re-written as:
var orders = repository.GetOrders(1234);
See the difference? Retrieved from Mr. Hamedani blog