{"record":{"id":"ddc4a40e4a280e97","repo":"fullstackhero/dotnet-starter-kit","slug":"product-command-productid-not-found-ddc4a4","errorCode":null,"errorMessage":"Product {command.ProductId} not found.","messagePattern":"Product (.+?) not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Catalog/Modules.Catalog/Features/v1/Products/UpdateProduct/UpdateProductCommandHandler.cs","lineNumber":20,"sourceCode":"using FSH.Framework.Core.Exceptions;\nusing FSH.Modules.Catalog.Contracts.v1.Products;\nusing FSH.Modules.Catalog.Data;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Catalog.Features.v1.Products.UpdateProduct;\n\npublic sealed class UpdateProductCommandHandler(CatalogDbContext dbContext)\n    : ICommandHandler<UpdateProductCommand, Guid>\n{\n    public async ValueTask<Guid> Handle(UpdateProductCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        var product = await dbContext.Products\n            .FirstOrDefaultAsync(p => p.Id == command.ProductId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Product {command.ProductId} not found.\");\n\n        if (product.BrandId != command.BrandId)\n        {\n            bool brandExists = await dbContext.Brands\n                .AnyAsync(b => b.Id == command.BrandId, cancellationToken)\n                .ConfigureAwait(false);\n            if (!brandExists)\n            {\n                throw new NotFoundException($\"Brand {command.BrandId} not found.\");\n            }\n        }\n\n        if (product.CategoryId != command.CategoryId)\n        {\n            bool categoryExists = await dbContext.Categories\n                .AnyAsync(c => c.Id == command.CategoryId, cancellationToken)\n                .ConfigureAwait(false);\n            if (!categoryExists)","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Catalog/Modules.Catalog/Features/v1/Products/UpdateProduct/UpdateProductCommandHandler.cs#L2-L38","documentation":"UpdateProductCommandHandler.Handle loads the product by ProductId and throws NotFoundException when FirstOrDefaultAsync finds nothing, mapping the miss to a 404 at the API layer. Same contract as other Catalog handlers: unknown aggregate id in a command = NotFound.","triggerScenarios":"UpdateProductCommand dispatched with a ProductId that doesn't exist in dbContext.Products (deleted product, wrong tenant, malformed id from the client).","commonSituations":"Editing a product that was deleted in another browser tab; stale list page submitting updates for removed rows; multitenant deployment where the id belongs to a different tenant.","solutions":["Confirm the product id via the GET product endpoint before updating.","Refresh the products list — the row may have been deleted concurrently.","Verify the request targets the right tenant; tenant filters hide other tenants' rows.","Fix client code that may be sending undefined/null mapped to a wrong Guid."],"exampleFix":"// before\nvar product = await db.Products.FindAsync(productId); // may be null → later NRE\nawait mediator.Send(new UpdateProductCommand(productId, body));\n// after\nif (!await db.Products.AnyAsync(p => p.Id == productId, ct))\n    return Results.NotFound();\nawait mediator.Send(new UpdateProductCommand(productId, body), ct);","handlingStrategy":"validation","validationCode":"bool exists = await dbContext.Products.AnyAsync(p => p.Id == command.ProductId, ct);\nif (!exists) return Results.NotFound();","typeGuard":"var product = await dbContext.Products.FirstOrDefaultAsync(p => p.Id == id, ct);\nif (product is null) return Results.NotFound();","tryCatchPattern":"try { await mediator.Send(updateCommand, ct); }\ncatch (NotFoundException) { return Results.NotFound($\"Product {id} not found\"); }","preventionTips":["Re-fetch entity lists before edit forms submit; don't trust row ids from stale pages.","Confirm ids are Guid-typed and non-empty before dispatching commands.","In optimistic UI, refetch on 404 and remove the row locally.","Check for concurrent deletes in collaborative tenants."],"tags":["catalog","not-found","update","cqrs"],"backgroundTag":"entity-not-found","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"}