fullstackhero/dotnet-starter-kit · error · NotFoundException

Category not found.

Error message

Category {command.CategoryId} not found.

What it means

DeleteCategoryCommandHandler loads the category by command.CategoryId and throws NotFoundException (HTTP 404) when no row matches, so the delete never runs. Soft-deleted and cross-tenant rows are excluded by the default query filters.

Solutions

  1. Confirm the CategoryId exists via the categories endpoint
  2. Refresh the list and retry with a valid ID
  3. Check tenant context if the category exists in another tenant
Defensive patterns

Strategy: try-catch

Validate before calling

try { await mediator.Send(new GetCategoryByIdQuery(id)); return true; }
catch (NotFoundException) { return false; }

Try / catch

try { await mediator.Send(new DeleteCategoryCommand(id)); }
catch (NotFoundException) { /* already deleted — no-op */ }

Prevention

When it happens

Trigger: Send/DeleteCategory command with a CategoryId that does not exist, was already deleted, or belongs to another tenant.

Common situations: Deleting from a stale grid after another user removed the category; wrong tenant header; passing a brand/product ID by mistake.

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

Appendix: source

Thrown at src/Modules/Catalog/Modules.Catalog/Features/v1/Categories/DeleteCategory/DeleteCategoryCommandHandler.cs:20

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

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

public sealed class DeleteCategoryCommandHandler(CatalogDbContext dbContext)
    : ICommandHandler<DeleteCategoryCommand, Unit>
{
    public async ValueTask<Unit> Handle(DeleteCategoryCommand command, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(command);

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

        bool hasChildren = await dbContext.Categories
            .AnyAsync(c => c.ParentCategoryId == category.Id, cancellationToken)
            .ConfigureAwait(false);
        if (hasChildren)
        {
            throw new CustomException(
                "Cannot delete a category that has child categories. Move or remove the children first.",
                (IEnumerable<string>?)null,
                HttpStatusCode.Conflict);
        }

        dbContext.Categories.Remove(category);
        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
        return Unit.Value;
    }
}

View on GitHub (pinned to 3f2959e683)