fullstackhero/dotnet-starter-kit · warning · KeyNotFoundException
Audit record not found.
Error message
Audit record {query.Id} not found. What it means
GetAuditByIdQueryHandler throws KeyNotFoundException when no AuditRecord with the requested Id exists in the database. The handler intentionally uses KeyNotFoundException (rather than the framework's NotFoundException) because audit exception-type fixtures and severity classification key off this type; it maps to HTTP 404 globally.
Solutions
- Verify the audit record Id exists and belongs to the current tenant (query the AuditRecords table).
- Check the X-Tenant header / tenant resolution so the query filter does not exclude the record.
- Handle 404 on the client and show a not-found state instead of treating it as a bug.
- If tests reference the id, re-seed audit data since fixtures are not persistent.
Example fix
// before
var audit = await client.GetAuditAsync("0e2f..."); // throws
// after
try
{
var audit = await client.GetAuditAsync(id);
}
catch (HttpRequestException) when (http.Response.StatusCode == HttpStatusCode.NotFound)
{
// record missing or in another tenant
} Defensive patterns
Strategy: try-catch
Validate before calling
var exists = await dbContext.AuditRecords.AnyAsync(a => a.Id == id); if (!exists) return NotFound();
Try / catch
try { var audit = await api.GetAuditById(id); } catch (Exception ex) when (ex is KeyNotFoundException || IsHttp404(ex)) { // show not-found UI } Prevention
- Confirm the record exists in the current tenant before deep-linking to it
- Refresh audit id references after retention cleanup jobs
- Treat 404 as a normal outcome in audit UIs, not an error banner
When it happens
Trigger: Calling the GET audit-by-id endpoint with an Id that does not exist, an Id belonging to another tenant (filtered out by the tenant query filter), or an already-deleted audit record.
Common situations: Stale client-side links after audit retention purges records; querying an audit id copied from a different tenant's environment; typos or case issues when constructing the id manually.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/c84f5a08cac63037.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Auditing/Modules.Auditing/Features/v1/GetAuditById/GetAuditByIdQueryHandler.cs:36
{
_dbContext = dbContext;
_logger = logger;
}
public async ValueTask<AuditDetailDto> Handle(GetAuditByIdQuery query, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
var record = await _dbContext.AuditRecords
.AsNoTracking()
.FirstOrDefaultAsync(a => a.Id == query.Id, cancellationToken)
.ConfigureAwait(false);
if (record is null)
{
// KeyNotFoundException maps to 404 globally. Kept (not framework NotFoundException)
// because audit exception-type fixtures and severity classification key off this type.
throw new KeyNotFoundException($"Audit record {query.Id} not found.");
}
JsonElement payload;
try
{
using var document = JsonDocument.Parse(record.PayloadJson);
payload = document.RootElement.Clone();
}
catch (JsonException ex)
{
_logger.LogWarning(ex, "Failed to parse audit payload JSON for record {AuditId}.", query.Id);
payload = JsonDocument.Parse("{}").RootElement.Clone();
}
return new AuditDetailDto
{
Id = record.Id,
OccurredAtUtc = record.OccurredAtUtc,View on GitHub (pinned to 3f2959e683)