There are a large number of examples on how to use the Generic Repository Pattern over Entitry Framework but none ever go into great detail. More often than not the example they use for accessing data from a webapi is a one to one mapping to the model.
For Eg. Get a single model object _carRepository<Car>.Get(id);
or get all cars _carRepository<Car>.GetAll();
The way I have it implemented is in seperate layers like below:
Controller -> Service Layer -> Repository Layer -> DataAccess
I have my service layer using AutoMapper where I map from the model(entity) to a DTO and that gets returned to the controller who returns that.
The issue that I cant get my head around is how do I return an object from the repository layer which isnt of type < T>.
Eg. I want to return some data for a grid lets say a combination of multipe entities like a customers last number of purchases. Am I supposed to pass the Customer, Product, Orders repository into the service and make multiple calls to the database and aggregate? Or I was thinking in the cutomers repository just make a method which returns like a view dto of sorts already aggreating the data where it does a linq query and projects to this dto. The issue I have is that I'm sure my repository shouldnt know about dto's?
Would appreciate anyone who has thoughts on the correct way of doing this?
CodePudding user response:
In short, I don't ever recommend using the Generic Repository (anti)pattern. The trouble is that it ends up being far too restrictive and inefficient. Methods like GetAll()
serve only to end up materializing entire tables into memory. They don't accommodate things like projection (essentially what you are looking for) as well as eager loading related data, filtering, sorting, or things like supporting pagination.
The way I recommend looking at repositories is as a one-to-one supplier of data for a Controller in the MVC pattern. If you have a CarController you have a CarRepository to serve all data for that controller. If you break it down to CarController and AddCarController then similarly you can build repositories to serve each of these. These repositories are not Generic in that they don't just serve Car entities, but any and all entities from the DbContext that this controller (or service) needs rather than trying to marry repositories to a specific entity. This gives them only one concern for their existance, so a CarController only needs to worry about primarily one repository, and that repository only has to worry about serving that controller. (Rather than every controller that might want information about a Car)
Regarding using a Service between the controller and repository, I would only suggest this if there is a distinct requirement to provide that separation. For example, if you want to support both an MVC controller and a public facing WebAPI and you want the inputs and outputs provided to these to be 100% consistent. The data transported from Service to Consumer (Controllers, etc.) would be DTOs. This adds the complexity of needing to send details like sorting, pagination, and filtering etc. from the consumer to the service.
Removing the service can make interacting with the repository a lot easier, and you can leverage projection to populate the DTOs/view models actually sent to the view via the controller actions/endpoints. The way I facilitate this is leveraging IQueryable<TEntity>
in the repository. This lets the repository handle low level filtering/rules if necessary and lets the consumer (controller) handle determining how it wants to consume the data.
For example if we have a CarController with a CarRepository and CarRepository has a method:
public IQueryable<Car> GetCars(bool includeInactive = false)
{
var query = _context.Cars.AsQueryable();
if (!includeInactive)
query = query.Where(x => x.IsActive);
return query;
}
When it comes time for the controller to request cars from the repository, it has full control over how to consume it:
var cars = await CarRepository.GetCars()
.Where(x => x.Make == make && x.Model == model)
.OrderBy(x => x.ModelYear)
.Select(x => new CarSummary
{
Make = x.Make,
Model = x.Model,
ModelYear = x.ModelYear,
Color = x.Color,
Features = x.Features.Select(...)
}.Skip(pageSize * (pageNumber-1))
.Take(pageSize)
.ToListAsync();
The controller has full control over how the data should come back including whether to run an async query or synchronous one all without adding any complexity to the repository. So here we can project (Select
, or leverage Automapper and ProjectTo
) to get whatever data we need from the entities and their related data. This leads to building far faster and memory/network efficient queries because the projections only worry about the data the end consumer actually needs. When we want to load an entity and its relations to perform an update, we can fetch the entity and eager load the related data we need to inspect/validate/update. Eager loading data is faster than lazy loading, but both use a fair bit of memory so we don't want to be serializing and transmitting entire object graphs. However, when doing an update we are typically only dealing with a single top-level object at a time.
The question then becomes "Why use a repository then?" The repository provides a nice abstraction for unit testing, and it can help standardize core rules you want in the system such as Active/Inactive (soft delete) and things like authorization in systems using multi-tenancy or otherwise having distinct access controls for the current user as to what data they should ever be able to see. If you don't have either of these requirements, then there isn't really much point to using a repository at all. The DbContext and it's DbSets essentially serve that role.
If you do have plans for multiple consumers, then the repository remains the same but the above consuming code happens in the Service, returning the DTO/ViewModel. This means that your controller will need to package and transmit standardized requests to the service or parameters covering whether it expects any sorting, pagination, etc. Again, I wouldn't recommend taking on that overhead and complexity unless there is a very real requirement justifying it. It just serves to make the code harder to work with.
CodePudding user response:
This may not be complete answer but few things while working with Generic Repository.
Assumption I made that your generic repository return type of T and T is the entity.
Following is not exact interface but based on your question.
public class IRepository<T>
{
T Get(int Id);
IEnumrable<T> GetAll();
}
So most of the scenario above will suffice the thing.
Now let's assume that you have requirement that will only return specific attribute of T or partial of T.
Generic repository implementation never say that you don't need any extra or extended repository.
So you can do like this.
public interface ICarRepository : IRepository<Car>
{
IList<int> GetIds();
IList<string> GetAllModels();
bool IsActiveInProduction(int carId);
}
Implement above just like you have implemented IRepository for EF core. Inject this in your services and Use ICarRepository instead of IRepository for your development.
Note: This is personal preference but for large project GenericRepository become issue sometime or may not completely fit but Repository is useful pattern and it overall depends on how you use it.
Update 1
In Car Repository if you want to return some other DTO but make sure it related to Car Entity otherwise it is not reponsibility of Repo layer.
public interface ICarRepository : IRepository<Car>
{
IList<int> GetIds();
IList<string> GetAllModels();
bool IsActiveInProduction(int carId);
MyDto GetMyDto(int carId); // MyDto is just for explanation.
}