Repository Pattern and Clean Architecture
Whenever we perform a query, the ideal approach is to return only the necessary columns.
However, whenever I see content about repositories, the SELECT is always done using the entity. In other words, the entire entity is selected from the database.
In my projects, I create responses or DTOs. But DTOs are recommended in the application layer, while repository interfaces belong to the domain layer.
Of course, I could map the entity to a DTO to protect sensitive data.
However, I would still be loading all the data from the database, including many unnecessary fields.
For example, in an online store:
On the homepage, we only display promotional products with their price, title, and image. It doesn't make sense to fetch the entire product description from the database.
18 Replies
You discovered one of the issues with repository pattern, yes
One of the reasons it's generally not recommended to use this pattern with EF
Because, to use it well... you would just re-implement what EF already gives you
Which is pointless
You have a point.
But how do you abstract the data you're getting?
Example... I have to get promotional products, I only need to show name and price (ProductSumamaryDto)... and I have 2 projects: Blazor / Api.
Both of them need to get promotional products.
The API queries the db and Blazor gets data from the API?
Unless you mean Blazor Server
Then you can use shared services
And have a method that specifically gets you this DTO
Or a CQRS handler
Or an IQueryable extension method
Well, I believe the best way would be a CQRS hadler on application layer, as you said.
For while, I'm using selector expression.
IEnumerable<T> GetDeals<T>(Expression<Func<Product, T>> selector);
it should be possible to map multiple entities to the same table, it's just tricky
but people never care and pull everything from the db
I have tried to achieve it once, could not do it
2 entities same table is easy, 3 didn't seem to work
I'm sure there is some workaround
You don't abstract the data you're getting, another reason to skip repository pattern. Each endpoint needs something unique and specific from the DB, trying to share a query between them will usually result in one of the two getting more data than it needs. Even if both Blazor and Api currently get the same data, there's no guarantee they always will be in sync as each one's needs evolve in the future, and sharing any part of their queries (or even using the same DTO for both) is usually a bad idea
If there is some guarantee and it's just identical code across them, then I'd focus on figuring out how to share that duplicated code as a whole, not just the query
wouldn't that kind of violate the core idea behind an ORM, that each table maps to a class in your code, and each class in your code maps to a table?
In case of inheritance it makes sense if you use Table Per Hierarchy approach then multiple inherited types would be in the same table, but besides that I think thats not a good idea, messes with the simplicity of an ORM.
There seems to be a way to do this https://learn.microsoft.com/en-us/ef/core/modeling/table-splitting#table-splitting
But then I would rather seperate into different DTOs, ViewModels or whatever the context than into different Domain Models and select only what's needed for the mapping tho 🤔
The idea is to make read and update models, not necessarily write models
To keep using ef core for this, but without having to overfetch
The thing you linked is what I tried to use to implement this
but you don't have to overfetch, you can just select or update what you want
The example they give is for 2 entities mapping to the same table
You do, because they won't be tracked otherwise
Any projection is still a projection
Even if it's to the same type
it doesn't scale for 3 entities as easily
"Track it after the fact" is what you're going to say
but when I pick specific properties in the Select then EF will translate it to select prop1, prop2, prop3 opposed to select * will it not?
might actually work, I'm not sure
yes but it won't track the materialized entity
because the select will count as a projection
even if you say
AsTracking
, it's going to ignore it
you're going to have to track them manually after the query, which I'm not sure is even going to work as expectedwell I'm doing a projection anyway then to some DTO or ViewModel or whatever, so even if it would be tracking the materialized entity it would be garbagecollected when the obj is out of scope anyway. So you gotta then map back to a domain model on your update, which seems fine
I'm afraid it might end up trying to save nulls where it shouldn't at some point or something like that, it calls for subtle issues
No need to materialize it if you're just going to map it later. The point of entities is that they're tracked by the system. Otherwise you can get away with projections for reads and
ExecuteUpdate
for updates.
"So you gotta then map back to a domain model on your update, which seems fine" map what back?
It's not "garbage collected anyway", it is extra memory you have to allocate that serves no purpose, it's not freeit will be garbage collected when the program leaves the scope you were in
I mean it will be garbage collected, but that is not free, you have to pay for it
gc overhead is not somethign I care about ^^
And it's not "when it leaves the scope", it is "at some unspecified time in the future, after it's left the scope"