0

I have an ASP.NET Blazor server-side project, using EF Core. One of the pages is getting quite large. Apart from any other reasons for keeping code files a reasonable size, the large size causes significant delays when recompiling.

I would like to split it down into smaller components, but the problem is that the whole page represents a fairly large object graph, parts of which are used in multiple places on the page.

Imagine a page that shows details for a company. The company has many employees, each of whom can claim expenses, which are added to the company's transactions list. The company itself has income and expense, so that adds more transactions. Other parts of the company object graph might also have associated expenses. This is a very simplified (and fictitious) sample of the idea. The page has various sections, such as one for employee details, which shows their transactions, as well as an overall transaction list.

At various places, you can add transactions, which get associated with the employee (or whatever), and are shown on both that transaction list and the main one. All of this is done with individual forms for each action, it's not one huge form for the whole object graph.

If I were to split the component down, I would be faced with one of the following choices (unless someone can suggest another)...

  1. Have each smaller component inject its own DbContext and handle its own data access. This is fine in theory, but would cause concurrency problems as it would mean that different components were saving changes to the same entities. It would also require a lot of events to inform the parent component that data had changed in the subcomponents, which would end up very messy.

  2. Have each smaller component have parameters for the bits of the object graph they handle. This avoids any concurrency issues, as only the parent component would be doing any data access. I'm not sure if it would avoid the need for events, as it depends on how well Blazor would notice if a part of the graph passed to a subcomponent changed.

  3. Pass the DbContext in to each smaller component as a parameter. Again, this avoids any concurrency issues, but really feels like the wrong way to do it.

Anyone able to guide me as to the best way to split this up?

Thanks

DreamingOfSleep
  • 1,208
  • 1
  • 11
  • 23
  • Are we talking here about using EF to manage complex business objects and `ObjectGraphDataAnnotationsValidator ` to validate said objects? – MrC aka Shaun Curtis Feb 06 '23 at 21:39
  • No, EF grabs the whole object graph from the database, and it's held in a local variable. Different parts of the markup bind to parts of the object graph. Any validation is done using regular validators on the individual parts of the graph.. – DreamingOfSleep Feb 06 '23 at 22:42
  • @MrCakaShaunCurtis Which could of course mean I'm doing it wrong. If so, please feel free to suggest a better way of doing this – DreamingOfSleep Feb 06 '23 at 22:43
  • In front of that screen sits a human being. Humans can [comprehend about 7 things at a time](https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two). – H H Feb 08 '23 at 20:57
  • With a good design, based on use-cases, this simply shouldn't come up. – H H Feb 08 '23 at 20:57
  • @HH I know what you're thinking, but it isn't quite that bad. The UI is all split up very clearly, so they are only ever looking at one part of the data at a time. My problem is that all the parts of the data are quite linked, which is why I'm having a problem splitting them – DreamingOfSleep Feb 08 '23 at 23:15

1 Answers1

0

If you have lots of sub-components accessing the data in the Form [your top level component - some sort of dashboard?] then you probably have quite a bit of plumbing to try and keep everything in sync, or lots of rendering going on if you are cascading objects. How often are you calling StateHasChanged?

Without some code I can only answer in very generic terms.

Your first step is to separate out your data and data management from your components and form. Move the data and the database into a DI service. The scope depends on what you're doing: Transient or Scoped. You can then use normal events to signal updates to components that need to render if something changes. There's an answer here that shows how to do this - https://stackoverflow.com/a/69562295/13065781.

[Opinionated] You also need to understand that by building a complex object (your DataGraph) and then letting EF manage it's state, [in Clean Design terms] you're building core application logic (the relationships between your basic data objects) into your infrastructure layer. The advantage is it makes things easy, and saves a lot of coding. The disadvantages come to light over time.

MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31
  • Thanks for the reply, I've been mulling this over for a couple of days, trying to work out what I should take from it. To clarify, I don't have the whole object graph in one form. Please see my updated question where I attempted to explain it a bit better. My problem with your suggestion of moving things out, is that it sounds like my option #1, where I would end up with different components or classes handling different bits of the object graph. (cont...) – DreamingOfSleep Feb 08 '23 at 20:24
  • As I said in my question, that would mean loads of events to signal changes, as well as concurrency issues when one component tried to change something that another component had changed since the first one loaded it. Maybe I'm not understanding your suggestion, and maybe (very likely) I'm approaching this the wrong way. Please could you explain in a bit more detail what you mean. Thanks again – DreamingOfSleep Feb 08 '23 at 20:26
  • Forgot to add that I had a play with the code in your linked answer. Very neat way of doing it, but in my case, I would have concurrency issues when different components updated the same bit of data. It also means lots of events, which I was hoping to avoid. – DreamingOfSleep Feb 08 '23 at 20:37
  • I'm lost. Are you saying you have a single object that represents a company, all it's employees, their expenses, it income and sales data,.... – MrC aka Shaun Curtis Feb 08 '23 at 23:14
  • That's not the actual model, but it's the same idea. There is a single parent object, and lots of child/grandchild/etc objects. Ideally I would split it into separate pages for different parts of the object graph, but the client needs it all available on the one page, and certain parts of the graph are linked to many other parts (eg transactions linked to the company, as well as employees, etc). – DreamingOfSleep Feb 09 '23 at 16:46
  • WOW, sounds like the antithesis of SOLID principles. Can you build a page with that much information on it! Best of luck! – MrC aka Shaun Curtis Feb 09 '23 at 19:36
  • I know it sounds like that, but the page is actually quite SOLID, in that all the business logic is separate from the UI (usually in injected dependencies). It's the fact that so many bits of the object graph depend on each other that means it's all in one page. There are tabs to separate all the logical sections, and each tab is moderately clean. There are quite a lot of places where small windows pop up for editing individual parts of the graph. – DreamingOfSleep Feb 12 '23 at 14:18
  • However, I'm totally open to suggestions on as to how to split it up without running into concurrency issues, and (preferably) without having to add loads of events to update the UI. – DreamingOfSleep Feb 12 '23 at 14:18
  • Interesting. Your UI sounds a little like a wilder idea I had a couple of years ago in lockdown, but never had a project to really develop - Build an application without routing, you're using Tabs. see - https://github.com/ShaunCurtis/Blazor-ViewManager. You're just loading your whole database in memory, and persisting it whenever necessary. – MrC aka Shaun Curtis Feb 12 '23 at 18:32
  • You should be able to measure how well your UI is working by how many times you resort to calling `StateHasChanged` and triggering render cascades to keep things updated. – MrC aka Shaun Curtis Feb 12 '23 at 18:39
  • In answer to your last question, almost none! I do call `StateHasChanged` a few times, but really not that many, and I don't trigger render cascades at all. – DreamingOfSleep Feb 13 '23 at 19:47
  • Do you have any up-to-date links for that tabs app you linked? The two links in the repo there don't work, the linked repo is devoid of code, and the repo that superseded it can't be found. Shame, was interested to see what you'd done – DreamingOfSleep Feb 13 '23 at 19:50