{"record":{"id":"49bd8d2b6e16a37c","repo":"fullstackhero/dotnet-starter-kit","slug":"a-category-cannot-be-its-own-parent","errorCode":null,"errorMessage":"A category cannot be its own parent.","messagePattern":"A category cannot be its own parent\\.","errorType":"exception","errorClass":"CustomException","httpStatus":400,"severity":"error","filePath":"src/Modules/Catalog/Modules.Catalog/Features/v1/Categories/UpdateCategory/UpdateCategoryCommandHandler.cs","lineNumber":26,"sourceCode":"namespace FSH.Modules.Catalog.Features.v1.Categories.UpdateCategory;\n\npublic sealed class UpdateCategoryCommandHandler(CatalogDbContext dbContext)\n    : ICommandHandler<UpdateCategoryCommand, Guid>\n{\n    public async ValueTask<Guid> Handle(UpdateCategoryCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        var category = await dbContext.Categories\n            .FirstOrDefaultAsync(c => c.Id == command.CategoryId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Category {command.CategoryId} not found.\");\n\n        if (command.ParentCategoryId is { } parentId)\n        {\n            if (parentId == category.Id)\n            {\n                throw new CustomException(\n                    \"A category cannot be its own parent.\",\n                    (IEnumerable<string>?)null,\n                    HttpStatusCode.BadRequest);\n            }\n\n            // Walk parent chain to detect cycles (parent → ancestor of self)\n            var visited = new HashSet<Guid> { category.Id };\n            Guid? cursor = parentId;\n            while (cursor is { } cur)\n            {\n                if (!visited.Add(cur))\n                {\n                    throw new CustomException(\n                        \"Setting this parent would create a cycle.\",\n                        (IEnumerable<string>?)null,\n                        HttpStatusCode.BadRequest);\n                }\n                cursor = await dbContext.Categories","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Catalog/Modules.Catalog/Features/v1/Categories/UpdateCategory/UpdateCategoryCommandHandler.cs#L8-L44","documentation":"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.","triggerScenarios":"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.","commonSituations":"Frontend dropdown not excluding the current category from parent options; bulk import mapping parent column incorrectly; API consumers constructing payloads programmatically.","solutions":["Exclude the category itself from the parent-selection dropdown in the UI.","Fix the payload so parentCategoryId differs from the category's id.","Send parentCategoryId = null if the intent was to make it a root category.","Add client-side validation rejecting parentId === id before submitting."],"exampleFix":"// before: dropdown includes self\n{categories.map(c => <option value={c.id}>{c.name}</option>)}\n\n// after: exclude self\n{categories.filter(c => c.id !== editingId).map(c => <option value={c.id}>{c.name}</option>)}","handlingStrategy":"validation","validationCode":"if (payload.parentCategoryId === categoryId) {\n  return formError('A category cannot be its own parent.');\n}","typeGuard":null,"tryCatchPattern":"try { await api.updateCategory(payload); }\ncatch (e) { if (e.status === 400) formError(e.message); else throw e; }","preventionTips":["Exclude the current category from its own parent dropdown","Treat empty parent selection as null (root), not as self-id","Add a form-level zod/refine rule parentId !== id"],"tags":["validation","hierarchy","bad-request","catalog"],"backgroundTag":"invalid-argument-value","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}