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
- Query the table directly (including hard-deleted rows) to confirm the row physically exists before restoring.
- Check the tenant context of the request matches the ticket's TenantId.
- If the ticket was hard-deleted, restore from backup or recreate the ticket instead of calling RestoreTicket.
- 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
- Distinguish soft-delete (restorable) from hard-delete (not restorable) in tooling.
- Verify ids come from the same tenant/environment you call.
- Validate GUID format client-side before submitting.
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)