{"record":{"id":"f59e61faa2efce23","repo":"fullstackhero/dotnet-starter-kit","slug":"ticket-command-ticketid-not-found-updateticketcommandhandler","errorCode":null,"errorMessage":"Ticket {command.TicketId} not found.","messagePattern":"Ticket (.+?) not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Tickets/Modules.Tickets/Features/v1/Tickets/UpdateTicket/UpdateTicketCommandHandler.cs","lineNumber":19,"sourceCode":"using FSH.Framework.Core.Exceptions;\nusing FSH.Modules.Tickets.Contracts.v1.Tickets;\nusing FSH.Modules.Tickets.Data;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Tickets.Features.v1.Tickets.UpdateTicket;\n\npublic sealed class UpdateTicketCommandHandler(TicketsDbContext dbContext)\n    : ICommandHandler<UpdateTicketCommand, Guid>\n{\n    public async ValueTask<Guid> Handle(UpdateTicketCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        var ticket = await dbContext.Tickets\n            .FirstOrDefaultAsync(t => t.Id == command.TicketId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Ticket {command.TicketId} not found.\");\n\n        ticket.UpdateDetails(command.Title, command.Description, command.Priority);\n        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n        return ticket.Id;\n    }\n}\n","sourceCodeStart":1,"sourceCodeEnd":26,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Tickets/Modules.Tickets/Features/v1/Tickets/UpdateTicket/UpdateTicketCommandHandler.cs#L1-L26","documentation":"UpdateTicket's handler fetches the ticket by TicketId with default (soft-delete + tenant) query filters and throws NotFoundException if no row matches. The handler refuses to update a nonexistent, soft-deleted, or out-of-tenant ticket, mapping the failure to the app's typed 404 exception.","triggerScenarios":"Submitting UpdateTicket (title/description/priority changes) for a TicketId that doesn't exist, was soft-deleted, or lives in another tenant; a stale UI form holding an id of a ticket deleted concurrently by another user.","commonSituations":"Two operators editing the same ticket list, one deletes while the other saves; admin app and dashboard app pointing at different tenants with a copied link; automated scripts replaying updates for tickets purged in the meantime.","solutions":["Re-fetch the ticket list to confirm the ticket still exists and is not deleted before updating.","If soft-deleted, call RestoreTicket first, then retry UpdateTicket.","Verify the tenant context (header/subdomain) matches the ticket's tenant.","Handle the 404 in the client (refresh form state) instead of blindly retrying."],"exampleFix":"// before\nawait mutateUpdate({ id: staleId, title, description, priority }); // 404\n// after\nconst res = await getTicket(staleId);\nif (!res) { refreshList(); notify('Ticket no longer exists'); return; }\nawait mutateUpdate({ id: staleId, title, description, priority });","handlingStrategy":"try-catch","validationCode":"const ticket = await api.getTicket(id);\nif (!ticket || ticket.deleted) { refreshForm(); return; }","typeGuard":null,"tryCatchPattern":"try {\n  await updateTicket({ id, title, description, priority });\n} catch (ApiError e) when (e.Status === 404) {\n  resetEditForm();\n  notify('Ticket was deleted by another user');\n}","preventionTips":["Refresh form data immediately before submitting edits.","Use optimistic-concurrency UI (reload on conflict/404).","Keep tenant headers consistent across list and update calls."],"tags":["ef-core","not-found","cqrs","tickets","soft-delete"],"backgroundTag":"entity-not-found","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}