fullstackhero/dotnet-starter-kit · error · NotFoundException

Ticket not found.

Error message

Ticket {command.TicketId} not found.

What it means

RestoreTicket's handler ignores the soft-delete query filter so it can see deleted tickets, but still throws NotFoundException when no row matches the TicketId at all. Soft-deleted rows ARE visible here, so this error means the ticket record is genuinely absent (or filtered out by tenant isolation), not merely deleted.

Solutions

  1. Query the table directly (including hard-deleted rows) to confirm the row physically exists before restoring.
  2. Check the tenant context of the request matches the ticket's TenantId.
  3. If the ticket was hard-deleted, restore from backup or recreate the ticket instead of calling RestoreTicket.
  4. Validate the id in the client before the call (non-empty GUID format).

Example fix

// before
await api.RestoreTicket(new RestoreTicketCommand(id)); // throws if row is gone
// after
try {
    await api.RestoreTicket(new RestoreTicketCommand(id));
} catch (ProblemDetailsException p) when (p.Status == 404) {
    logger.LogWarning("Ticket {TicketId} no longer exists; cannot restore", id);
}
Defensive patterns

Strategy: validation

Validate before calling

const row = await api.queryTickets({ includeDeleted: true, id });
if (!row) throw new SkipError('Row physically absent; restore impossible');

Try / catch

try {
  await restoreTicket(id);
} catch (ApiError e) when (e.Status === 404) {
  logger.warn('Cannot restore, ticket was hard-deleted');
}

Prevention

When it happens

Trigger: Calling RestoreTicket with a TicketId that does not exist in the table at all; the row exists but belongs to a different tenant (tenant query filter still applies since only the SoftDelete filter is ignored); the id is malformed/another entity's id.

Common situations: Attempting to restore a ticket that was hard-deleted (e.g. removed by a purge/cleanup job rather than soft-deleted); restore after a database was re-seeded so old ids vanished; cross-tenant restore attempt where the tenant context is wrong; typos when copying a GUID.

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/5172fe637ef61957. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Tickets/Modules.Tickets/Features/v1/Tickets/RestoreTicket/RestoreTicketCommandHandler.cs:21

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

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

public sealed class RestoreTicketCommandHandler(TicketsDbContext dbContext)
    : ICommandHandler<RestoreTicketCommand, Guid>
{
    public async ValueTask<Guid> Handle(RestoreTicketCommand command, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(command);

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

        ticket.Restore();
        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
        return ticket.Id;
    }
}

View on GitHub (pinned to 3f2959e683)