fullstackhero/dotnet-starter-kit · error · CustomException

A category cannot be its own parent.

Error message

A category cannot be its own parent.

What it means

UpdateCategory validates that a category's new ParentCategoryId is not the category's own id, throwing CustomException with 400 BadRequest. A self-parent would make the hierarchy ill-formed, so it is rejected up front before cycle walking.

Solutions

  1. Exclude the category itself from the parent-selection dropdown in the UI.
  2. Fix the payload so parentCategoryId differs from the category's id.
  3. Send parentCategoryId = null if the intent was to make it a root category.
  4. Add client-side validation rejecting parentId === id before submitting.

Example fix

// before: dropdown includes self
{categories.map(c => <option value={c.id}>{c.name}</option>)}

// after: exclude self
{categories.filter(c => c.id !== editingId).map(c => <option value={c.id}>{c.name}</option>)}
Defensive patterns

Strategy: validation

Validate before calling

if (payload.parentCategoryId === categoryId) {
  return formError('A category cannot be its own parent.');
}

Try / catch

try { await api.updateCategory(payload); }
catch (e) { if (e.status === 400) formError(e.message); else throw e; }

Prevention

When it happens

Trigger: PATCH update where the body's parentCategoryId equals the id in the route/body — e.g. a form that lets the category select itself, or a client echoing the id into the parent field when the user clears the parent selection.

Common situations: Frontend dropdown not excluding the current category from parent options; bulk import mapping parent column incorrectly; API consumers constructing payloads programmatically.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/49bd8d2b6e16a37c. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Catalog/Modules.Catalog/Features/v1/Categories/UpdateCategory/UpdateCategoryCommandHandler.cs:26

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

public sealed class UpdateCategoryCommandHandler(CatalogDbContext dbContext)
    : ICommandHandler<UpdateCategoryCommand, Guid>
{
    public async ValueTask<Guid> Handle(UpdateCategoryCommand 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.");

        if (command.ParentCategoryId is { } parentId)
        {
            if (parentId == category.Id)
            {
                throw new CustomException(
                    "A category cannot be its own parent.",
                    (IEnumerable<string>?)null,
                    HttpStatusCode.BadRequest);
            }

            // Walk parent chain to detect cycles (parent → ancestor of self)
            var visited = new HashSet<Guid> { category.Id };
            Guid? cursor = parentId;
            while (cursor is { } cur)
            {
                if (!visited.Add(cur))
                {
                    throw new CustomException(
                        "Setting this parent would create a cycle.",
                        (IEnumerable<string>?)null,
                        HttpStatusCode.BadRequest);
                }
                cursor = await dbContext.Categories

View on GitHub (pinned to 3f2959e683)