fullstackhero/dotnet-starter-kit · warning · NotFoundException

Category not found.

Error message

Category {query.CategoryId} not found.

What it means

GetCategoryById throws NotFoundException when no category with the requested CategoryId exists in the Categories table. The lookup uses AsNoTracking with the default query filters, so soft-deleted categories are also filtered out and produce the same 'not found' result. It is a normal 404-style outcome, not a bug.

Solutions

  1. Verify the CategoryId Guid is correct and exists: SELECT * FROM "Categories" WHERE "Id" = '<id>'.
  2. Check whether the row is soft-deleted (DeletedOnUtc IS NOT NULL) and restore it if needed.
  3. Confirm the request is made against the intended tenant so the global query filter matches the row.
  4. Ensure the client is pointed at the right environment/database connection string.

Example fix

// before (client assumes category always exists)
var category = await api.getCategory(id);
render(category);

// after
var category = await api.getCategory(id); // throws NotFound -> handled
if (category is null) { show("Category no longer exists"); return; }
Defensive patterns

Strategy: try-catch

Validate before calling

// client-side pre-check before deep-linking
if (!/^[0-9a-f-]{36}$/i.test(categoryId)) return reject('invalid category id');
const exists = await api.listCategories({ ids: [categoryId] });
if (!exists.items.length) return show('Category not found');

Type guard

const isGuid = (v) => typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);

Try / catch

try { const c = await api.getCategory(id); render(c); }
catch (e) { if (e.status === 404) show('Category not found or deleted'); else throw e; }

Prevention

When it happens

Trigger: GET category by id where: (1) the Guid is wrong or from another environment, (2) the category was soft-deleted (DeletedOnUtc set, filtered by the SoftDelete query filter), (3) the category belongs to a different tenant than the request's tenant context.

Common situations: Stale UI links/bookmarks to a deleted category; copying ids between dev/staging/prod databases; multi-tenant apps where the id exists in another tenant; tests seeding one database but querying another.

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

Appendix: source

Thrown at src/Modules/Catalog/Modules.Catalog/Features/v1/Categories/GetCategoryById/GetCategoryByIdQueryHandler.cs:21

using FSH.Modules.Catalog.Contracts.v1.Categories;
using FSH.Modules.Catalog.Data;
using Mediator;
using Microsoft.EntityFrameworkCore;

namespace FSH.Modules.Catalog.Features.v1.Categories.GetCategoryById;

public sealed class GetCategoryByIdQueryHandler(CatalogDbContext dbContext)
    : IQueryHandler<GetCategoryByIdQuery, CategoryDto>
{
    public async ValueTask<CategoryDto> Handle(GetCategoryByIdQuery query, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(query);

        var c = await dbContext.Categories
            .AsNoTracking()
            .FirstOrDefaultAsync(c => c.Id == query.CategoryId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException($"Category {query.CategoryId} not found.");

        return new CategoryDto(c.Id, c.Name, c.Slug, c.Description, c.ParentCategoryId, c.CreatedAtUtc, c.UpdatedAtUtc, c.DeletedOnUtc, c.DeletedBy);
    }
}

View on GitHub (pinned to 3f2959e683)