5.2 KiB
Plan: Prospect list performance overhaul
The current bottleneck is not just data volume; it is the combination of loading every prospect and every nested interaction at once, then rendering all cards in the browser. In the current flow, each page loads the full list from ProspectService.GetProspectsAsync, includes all Images and Interactions plus each User, and then ProspectGrid renders every card through Repeater without virtualization. With hundreds of records, the UI does repeated LINQ work per card and hits a lot of DOM work in one render.
Recommended approach
Use a two-part fix: reduce the server-side payload and virtualize the client-side rendering. The best gains will come from paginating or virtualizing the list, then trimming unnecessary nested load and repeated per-row calculations.
Steps
-
Profile the bottleneck and cap the visible list size
- Confirm the prospect pages are loading all records at once because
GetProspectsAsynccurrently doesToListAsync()without paging or limit. - Add a page-size/virtualized flow so only the visible slice is fetched and rendered.
- This change must happen in the page and service layer before any UI refactor.
- Confirm the prospect pages are loading all records at once because
-
Reduce EF payload in
ProspectService.GetProspectsAsync- Update
FoodsharingSiegen.Server/Data/Service/ProspectService.csso it accepts pagination arguments (skip,take, optional filters) instead of returning the full unbounded list. - Keep the query as
AsNoTracking()but avoid eager-loading allImagesand everyUserfor every prospect when the page only needs a summary view. - For the list pages, load only the subset needed for the grid and defer expensive image/user data until a detail action opens.
- Filter and sort should happen server-side when possible to avoid moving hundreds of full entities into memory and then filtering again in the browser.
- Update
-
Replace the full list render with virtualization
- Convert the repeated list in
FoodsharingSiegen.Server/Controls/ProspectGrid.razorto a virtualized list or paged container, rather than rendering a full<Repeater Items="@Prospects">for all rows. - Keep the visible cards only; the hidden rows should not be in the DOM.
- This removes the biggest DOM cost on pages with hundreds of entries.
- Convert the repeated list in
-
Remove repeated per-row computation in the card components
- In
FoodsharingSiegen.Server/Controls/InteractionRow.razor.cs, the propertiesInteractions,Done,Alert,NotNeededrecalculate on every render from a list of nested items. With hundreds of cards this becomes expensive. - Replace repeated
Where()chains with precomputed values from the loaded model or cached values at the prospect object level. - In
FoodsharingSiegen.Contracts/Entity/Prospect.cs, avoid derivingCompletefromInteractions.Any()on every render when a summary value is already known in the data model. - Keep the card logic free of repeated
ToList()andAny()chains during render.
- In
-
Tighten data shaping for each list page
- The onboarding, verification, archive, and done pages all load the same full set of entities and then apply client-side filters with
ApplyFilterandApplySortinFoodsharingSiegen.Shared/Helper/FilterHelper.cs. - Move the expensive filtering/sorting to SQL or at least to the server-side query layer; reduce the list to a smaller result set before it reaches the UI.
- Keep
ProspectList.ApplyFilteronly as a fallback for small local slices, not as the primary path for large lists.
- The onboarding, verification, archive, and done pages all load the same full set of entities and then apply client-side filters with
-
Verify the improvement with focused checks
- Measure page load time before and after with a realistic dataset of several hundred prospects.
- Validate that the list stays responsive during scrolling and interaction changes.
- Run the relevant .NET tests and render-check the prospect pages manually.
Relevant files
FoodsharingSiegen.Server/Data/Service/ProspectService.cs— main query and database fetch point.FoodsharingSiegen.Server/Controls/ProspectGrid.razor— full-list rendering path.FoodsharingSiegen.Server/Controls/ProspectContainer.razor— per-card DOM and nested component rendering.FoodsharingSiegen.Server/Controls/InteractionRow.razor.cs— repeated LINQ work in each row.FoodsharingSiegen.Contracts/Entity/Prospect.cs— computed values that trigger repeated work.FoodsharingSiegen.Shared/Helper/FilterHelper.cs— client-side list filtering/sorting on large sets.FoodsharingSiegen.Server/Pages/Prospects.razor,FoodsharingSiegen.Server/Pages/ProspectsAll.razor,FoodsharingSiegen.Server/Pages/ProspectsVerify.razor,FoodsharingSiegen.Server/Pages/ProspectsDone.razor— list page orchestration and data flow.
Key decision
The highest-value fix is to stop returning and rendering all prospects at once. A virtualized/paged list with a trimmed query shape will provide the biggest speedup and should be the first implementation target.
Optional follow-up improvements
- Precompute a lightweight summary model for the grid instead of the full
Prospectentity. - Add a server-side count query to support proper paging without loading every record.
- Consider splitting the list into “summary” and “detail” data so the main dashboard never loads full nested relationship graphs by default.