fullstackhero/dotnet-starter-kit · error · NotFoundException

Ticket not found.

Error message

Ticket {command.TicketId} not found.

What it means

DeleteTicketCommandHandler looks up the ticket by command.TicketId and throws NotFoundException (404) if absent, before performing the soft delete (audit interceptor flips IsDeleted). A missing id means nothing was deleted.

Solutions

  1. Treat a second 404 on the same id as idempotent success in the client and refresh the list.
  2. Verify the ticket id exists before issuing the delete.
  3. Catch NotFoundException and log rather than crash in batch cleanup jobs.
  4. Confirm the caller's tenant matches the ticket's tenant.

Example fix

// before
deleteTicket.mutate(id); // second click throws NotFoundException
// after
const onDelete = (id: string) => deleteTicket.mutate(id, {
  onError: (e) => { if (isNotFound(e)) queryClient.invalidateQueries(['tickets']); }
});
Defensive patterns

Strategy: validation

Validate before calling

var ticket = await dbContext.Tickets.FirstOrDefaultAsync(t => t.Id == id);
if (ticket is null) throw new KeyNotFoundException($"Ticket {id} not found; nothing deleted.");

Type guard

static bool Deletable(Ticket? t) => t is { IsDeleted: false };

Try / catch

try { await mediator.Send(new DeleteTicketCommand { TicketId = id }); }
catch (NotFoundException) { /* already gone — treat as idempotent success */ }

Prevention

When it happens

Trigger: DeleteTicketCommand with an unknown TicketId, an id already hard-removed, or a ticket invisible due to tenant filtering.

Common situations: Double-click Delete causing a second call after the first soft-deleted the ticket (if filtered out on re-query); cleanup scripts with stale ids; cross-tenant id collision.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/5aec21fc4a864db2. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Tickets/Modules.Tickets/Features/v1/Tickets/DeleteTicket/DeleteTicketCommandHandler.cs:19

using FSH.Framework.Core.Exceptions;
using FSH.Modules.Tickets.Contracts.v1.Tickets;
using FSH.Modules.Tickets.Data;
using Mediator;
using Microsoft.EntityFrameworkCore;

namespace FSH.Modules.Tickets.Features.v1.Tickets.DeleteTicket;

public sealed class DeleteTicketCommandHandler(TicketsDbContext dbContext)
    : ICommandHandler<DeleteTicketCommand, Unit>
{
    public async ValueTask<Unit> Handle(DeleteTicketCommand command, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(command);

        var ticket = await dbContext.Tickets
            .FirstOrDefaultAsync(t => t.Id == command.TicketId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException($"Ticket {command.TicketId} not found.");

        // Soft delete: the audit interceptor converts the EF Delete into an IsDeleted flip.
        // Comments are not auto-included, so they are left untouched and survive a Restore.
        dbContext.Tickets.Remove(ticket);
        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
        return Unit.Value;
    }
}

View on GitHub (pinned to 3f2959e683)