{"record":{"id":"71d3b343b72b17d1","repo":"fullstackhero/dotnet-starter-kit","slug":"a-brand-with-name-command-name-already-exists","errorCode":null,"errorMessage":"A brand with name '{command.Name}' already exists.","messagePattern":"A brand with name '(.+?)' already exists\\.","errorType":"http","errorClass":"CustomException","httpStatus":409,"severity":"error","filePath":"src/Modules/Catalog/Modules.Catalog/Features/v1/Brands/CreateBrand/CreateBrandCommandHandler.cs","lineNumber":25,"sourceCode":"using Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Catalog.Features.v1.Brands.CreateBrand;\n\npublic sealed class CreateBrandCommandHandler(CatalogDbContext dbContext)\n    : ICommandHandler<CreateBrandCommand, Guid>\n{\n    public async ValueTask<Guid> Handle(CreateBrandCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        var brand = Brand.Create(command.Name, command.Description, command.LogoUrl);\n\n        bool slugTaken = await dbContext.Brands\n            .AnyAsync(b => b.Slug == brand.Slug, cancellationToken)\n            .ConfigureAwait(false);\n        if (slugTaken)\n        {\n            throw new CustomException(\n                $\"A brand with name '{command.Name}' already exists.\",\n                (IEnumerable<string>?)null,\n                HttpStatusCode.Conflict);\n        }\n\n        dbContext.Brands.Add(brand);\n        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n        return brand.Id;\n    }\n}\n","sourceCodeStart":7,"sourceCodeEnd":36,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Catalog/Modules.Catalog/Features/v1/Brands/CreateBrand/CreateBrandCommandHandler.cs#L7-L36","documentation":"CreateBrandCommandHandler throws a CustomException with HTTP 409 Conflict when a brand whose computed Slug collides with the new brand's Slug already exists in the Catalog database. The check runs before the new brand is added, so nothing is persisted. The message mentions the brand Name because the slug is derived from it.","triggerScenarios":"POST/CreateBrand command whose Name (after slug normalization) matches the Slug of an existing brand row in dbContext.Brands.","commonSituations":"Re-creating a brand that was created before; creating two brands whose names differ only by case, punctuation or whitespace and therefore slugify identically; running seed/import scripts twice.","solutions":["Pick a different brand name (or explicitly supply a distinct slug) and retry","Query GET /brands/v1 to confirm the existing brand — if it is the same brand you want, update it instead of creating a new one","If the conflicting row is a soft-deleted brand, restore or permanently remove it via the Restore/Delete endpoints"],"exampleFix":"// before\nawait mediator.Send(new CreateBrandCommand(\"Acme\")); // again\n// after\nawait mediator.Send(new UpdateBrandCommand(existingId, \"Acme\", description, logoUrl));","handlingStrategy":"validation","validationCode":"var slugTaken = await apiClient.GET<bool>($\"/brands/v1/exists?slug={Uri.EscapeDataString(slug)}\");\nif (slugTaken) throw new InvalidOperationException($\"Brand name '{name}' is already in use.\");","typeGuard":"bool IsUsableName(string? name) => !string.IsNullOrWhiteSpace(name) && name.Trim().Length >= 2;","tryCatchPattern":"try { await mediator.Send(new CreateBrandCommand(name)); }\ncatch (CustomException ex) when (ex.StatusCode == HttpStatusCode.Conflict) { /* surface 'name already exists' to the user */ }","preventionTips":["Check the slug-availability endpoint before submitting","Normalize names (trim/case) before creating to avoid slug collisions","Handle idempotency in seed scripts — look up before insert"],"tags":["conflict","http-409","catalog","uniqueness"],"backgroundTag":"duplicate-name-conflict","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"}