From 336bbb1dec17dfe50d812eac89829ec60b94ba1f Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Tue, 10 Mar 2026 11:12:28 -0400 Subject: [PATCH] Refactor Event components for improved data handling and null safety This commit updates the Edit.razor and EventAttributes.razor components to enhance data handling and prevent potential null reference exceptions. In Edit.razor, comments are added to clarify the tracking of related careers, ensuring that the same instances are used to avoid tracking conflicts. The EventAttributes.razor component is modified to handle null EventDefinition gracefully, preventing errors when the parameter is not set. Additionally, the EventDefinitionService is updated to ensure existing careers are retrieved with tracking, improving data consistency. These changes collectively enhance the robustness and reliability of the event management features. --- .../Events/Components/EventAttributes.razor | 13 +++++++++++-- WebApp/Components/Features/Events/Edit.razor | 9 ++++++--- WebApp/Components/Features/Teams/Printout.razor | 2 ++ WebApp/Services/EventDefinitionService.cs | 6 ++++-- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/WebApp/Components/Features/Events/Components/EventAttributes.razor b/WebApp/Components/Features/Events/Components/EventAttributes.razor index c2ff455..65eaa62 100644 --- a/WebApp/Components/Features/Events/Components/EventAttributes.razor +++ b/WebApp/Components/Features/Events/Components/EventAttributes.razor @@ -1,5 +1,9 @@ -@using WebApp.Models +@using WebApp.Models +@if (EventDefinition is null) +{ + return; +} @* @if (EventDefinition.LevelOfEffort.HasValue) { @EventDefinition.LevelOfEffort @@ -27,12 +31,17 @@ @code { [Parameter] - public required EventDefinition EventDefinition { get; set; } + public EventDefinition? EventDefinition { get; set; } private string _attributes = string.Empty; protected override void OnParametersSet() { + if (EventDefinition is null) + { + _attributes = string.Empty; + return; + } _attributes = EventDefinition.EventFormat == EventFormat.Individual ? AppIcons.IndividualEvent : " "; _attributes += EventDefinition.OnSiteActivity ? AppIcons.OnSiteActivity : " "; _attributes += EventDefinition.RegionalEvent ? AppIcons.RegionalEvent : " "; diff --git a/WebApp/Components/Features/Events/Edit.razor b/WebApp/Components/Features/Events/Edit.razor index 63c5a93..a2303fd 100644 --- a/WebApp/Components/Features/Events/Edit.razor +++ b/WebApp/Components/Features/Events/Edit.razor @@ -1,4 +1,4 @@ -@page "/events/edit" +@page "/events/edit" @attribute [Authorize] @using Microsoft.EntityFrameworkCore @using WebApp.Components.Shared.Components @@ -163,9 +163,10 @@ { try { - // Get the tracked entity from the database + // Get the tracked entity from the database (do not Include RelatedCareers: + // the same context may already be tracking those Career instances from the initial load, + // which would cause "another instance with the same key value is already being tracked"). var trackedEntity = await context.Events - .Include(e => e.RelatedCareers) .FirstOrDefaultAsync(e => e.Id == EventDefinition!.Id); if (trackedEntity == null) @@ -177,6 +178,8 @@ // Update scalar properties from the form-bound entity context.Entry(trackedEntity).CurrentValues.SetValues(EventDefinition!); + // RelatedCareersText is not mapped; copy it so ProcessRelatedCareersAsync can use it + trackedEntity.RelatedCareersText = EventDefinition!.RelatedCareersText; // Normalize and process related careers await EventDefinitionService.ProcessRelatedCareersAsync(trackedEntity); diff --git a/WebApp/Components/Features/Teams/Printout.razor b/WebApp/Components/Features/Teams/Printout.razor index 7c4c84d..3aa9310 100644 --- a/WebApp/Components/Features/Teams/Printout.razor +++ b/WebApp/Components/Features/Teams/Printout.razor @@ -244,6 +244,8 @@ else .AsNoTracking() .Include(e => e.Teams) .ThenInclude(e => e.Captain) + .Include(e => e.Teams) + .ThenInclude(e => e.Event) .Include(e => e.EventRankings) .ThenInclude(e => e.EventDefinition) .OrderBy(e => e.FirstName).ToArrayAsync(); diff --git a/WebApp/Services/EventDefinitionService.cs b/WebApp/Services/EventDefinitionService.cs index da63e6a..8ba4d5e 100644 --- a/WebApp/Services/EventDefinitionService.cs +++ b/WebApp/Services/EventDefinitionService.cs @@ -38,9 +38,11 @@ public class EventDefinitionService return; } - // Get all existing careers from database (case-insensitive lookup) + // Get existing careers with tracking so we use the same instances the context + // may already be tracking (e.g. from the event's RelatedCareers). Using + // AsNoTracking() would create duplicate instances and cause "another instance + // with the same key value is already being tracked". var existingCareers = await _context.Careers - .AsNoTracking() .Where(c => !string.IsNullOrWhiteSpace(c.Name)) .ToListAsync();