C
C#•13mo ago
ky1e

How to map Entity Framework entities containing circular references to DTOs?

When querying data with Entity Framework, the result data appears to contain circular references. (Using the example data from the attached screenshots) If I query a Sport and include its Leagues, the result is a Sport with list of Leagues, where each League is associated with a Sport, and that Sport is associated with a list of Leagues... you get the idea. With the screenshotted mapper classes, attempting to convert the Sport entity to a DTO causes the app to crash. Makes sense. If i remove one of the related DTOs from the other DTO's definition, the mapper has no problem, but this affects the flexibility I'd like to have. I would also like to avoid using AutoMapper or other third-party libraries. What is the solution to this? Is there something I can/should do with Entity Framework to avoid circular references in the query results? Or is there a way to set up mapper classes to handle this in a way that prevents redundant data from being mapped? (e.g. do not map the Sport inside League when the League was retrieved from its Sport parent).
No description
No description
15 Replies
Saber
Saber•13mo ago
the obvious solution is to not have circluar references in your dtos
Angius
Angius•13mo ago
Entities might need circular references to configure the relationships DTOs don't You can break the reference circle wherever you want Sport has a league, cool. League doesn't have to contain sport, though
ky1e
ky1eOP•13mo ago
not sure how you can assume i dont have any use for having Sport in the League DTO without knowing anything about the app. but im gonna guess the suggestion would be to create a separate DTO for that? i imagine that would get really messy. i dont recall having this issue with AutoMapper, which leads me to think that library handles this in some way, but could be misremembering since it was long ago
Angius
Angius•13mo ago
So you get a League to the frontend, that League contains Sports. Sure, I can see that. List a League details and what sports are contained with it. If you want to check which Leagues a Sport is in, though, I'd do that in a different view/endpoint/whatever Instead of recursively and cyclically loading, seemingly, everything
ky1e
ky1eOP•13mo ago
it's a large scale app with ~100 entities and tons of relationships amongst them. i pulled these two for the example because they are the most basic, but there are tons of instances where this comes up. another example would be Player and Game. in some cases, the Player is being queried and the Player's games are included. in other cases, the Game is queried and needs to include the Players. this doesn't seem like anything atypical to me... can't help but think there's some way to manage this better than avoiding it entirely. even if the answer is to create multiple entity -> DTO mapper classes for a single entity
mg
mg•13mo ago
I would either share DTOs and set the nested Player -> Game -> Players property to null, or have different DTOs
Angius
Angius•13mo ago
Yeah, in those cases you have a PlayerDto with a reference to GameDto, and that one has no reference back to PlayerDto And in the other case, you have a GameDto with a reference to PlayerDto, but this one has no reference back to GameDto A separate set for each case They're supposed to represent the data you expect to get They're not necessarily meant to be reusable I'd even argue that they shouldn't be
ky1e
ky1eOP•13mo ago
i saw someone's solution posted online was to null out the circular references after querying the entity. seems janky i'll prob create separate DTOs if im not able to find any better approach. i appreciate your guys input
Angius
Angius•13mo ago
Use records to save yourself some typing Ah, wait, you are lol I wonder why the explicit properties, though, instead of using the primary ctor
ky1e
ky1eOP•13mo ago
probably a skill issue 😉
mg
mg•13mo ago
I think I ended up doing this when I had to serialize some circular dependencies I was doing a lot of very wrong things along with that, but that in particular I don't think was too bad
JP
JP•13mo ago
Some methods to consider: 1) Separate routes per resource, make an extra request, but simplify your object model and limit the # of routes required. This is a slight perf hit because of the extra trip, but can simplify things massively. - Want a player and their games? Query GET /Players/:playerid, GET /Games?playerId=:playerid 2) As Ky1e suggested, route per permutation. If you truly have this scenario a lot, this can get big quick. 3) You truly have an insanely complicated app, with hundreds of required permutations from the frontend. Investigate if exposing a query language like GraphQL is worth the investment. Personally, I default to #1, and use #2 (creating specific routes for a specific frontend component, plucking exactly what I need) in cases where there is a lot of aggregation happening, or performance is critical.
ky1e
ky1eOP•13mo ago
i appreciate the input. i have considered looking closer at GraphQL because of many instances where various front-end views need similar, but slightly different, forms of some data (e.g. each view uses the same base entity but one view needs related entities a, b, c, while a different view needs related entities x, y, z) im not very knowledgeable about GraphQL. would using GraphQL eliminate my issue with circular references entirely (and thus resulting in my mapper classes working fine?), or would it eliminate my need for mapper classes, or is that more of an unrelated suggestion based on the app description?
JP
JP•13mo ago
GraphQL would be a pretty large restructure. Probably replacing most of what you have. Definitely a nuclear option, and one that would warrant research My personal solution is closer to #1, where I make most resources filterable based on the related entities. Extra trip, and a per-route complexity increase for filtering, but you cut down the number of routes a lot in my exp.
PixxelKick
PixxelKick•13mo ago
You need to use bespoke DTOs, don't try to keep re-using the same DTOs. Your DTOs should explicitly only have precisely the data you need to expose for that specific endpoint. If that means making a bespoke LeagueDTO for your sports endpoint, that is different from the one used in your League endpoints, so be it. Typically I try and avoid "oversharing" on my API endpoints.
Want results from more Discord servers?
Add your server